Prechádzať zdrojové kódy

docs(editor): rewrite AGENTS.md rule guide with three registration points and MarkdownPattern example

- Replace flat step list with three-file registration table
  (markdown-extensions.ts → inline-rules.ts/block-rules.ts → engine.ts)
- Add ::foo:: MarkdownPattern end-to-end example covering
  Lezer node definition, decoration rule factory, and registration
- Update registration example to use extraInlineRules/extraBlockRules
  constructor params instead of hardcoded engine.ts approach
- Link to @lezer/markdown MarkdownConfig API reference
Zander Hawke 3 dní pred
rodič
commit
8d945dde60
1 zmenil súbory, kde vykonal 90 pridanie a 34 odobranie
  1. 90 34
      AGENTS.md

+ 90 - 34
AGENTS.md

@@ -119,67 +119,123 @@ Tables use a **two-state rendering pattern**: blurred → source hidden, HTML wi
 
 ## How to Add a New Rule
 
-Follow these steps exactly. Do not deviate from this pattern.
+There are **three registration points** in the pipeline. Every new syntax touches all three.
 
-**1. Identify the pipeline stage.**
-- Inline syntax (spans within a line, e.g. bold, links) → `InlineRule` (defined as `MarkdownRule<unknown>`)
-- Line/block syntax (whole-line patterns, e.g. headings, blockquotes) → `BlockRule`
-- Key-value extraction → add to `parseProperties()` in `engine.ts`
+| # | File | Purpose |
+|---|---|---|
+| 1 | `markdown-extensions.ts` | Define the Lezer node type via `MarkdownConfig` (so the parser produces AST nodes for your syntax) |
+| 2 | `inline-rules.ts` / `block-rules.ts` | Create a `MarkdownRule<unknown>` or `BlockRule` factory that matches the new node type and returns decorations |
+| 3 | `engine.ts` (or consumer code) | Register the rule via the `extraInlineRules`/`extraBlockRules` constructor param, or add to `createInlineRules()`/`createBlockRules()` for built-in syntax |
+
+Follow the example below — it adds a `::foo::` inline syntax end to end.
+
+---
+
+### Step 1 — Define the Lezer node (`markdown-extensions.ts`)
 
-**2. Implement a self-contained factory function** in the appropriate file under `packages/editor/src/lib/markdown-rules/`. Do not modify `MarkdownRuleEngine.parse()`.
+If your syntax isn't already a standard GFM node, add a `MarkdownPattern` entry to `defaultPatterns` or `createMarkdownExtensions()`:
 
 ```typescript
-// Example: a minimal InlineRule factory (add to inline-rules.ts)
-function createMyInlineRule(): MarkdownRule<unknown> {
+// markdown-extensions.ts — add to defaultPatterns array
+{
+  name: "FooMarker",
+  start: "::",
+  end: "::",
+  nodeType: "FooMarker",
+  decoration: {
+    contentClass: "md-foo",
+    hiddenClass: "md-hidden",
+  },
+}
+```
+
+This creates a `"FooMarker"` Lezer inline node for text between `::` delimiters. For block-level syntax (e.g. `::component{attrs}`), use a `LeafBlockParser` instead — see the existing `TaskParser` or [MdcParser in the MDC plan](.agents/plans/mdc.md).
+
+**Reference:** [@lezer/markdown MarkdownConfig API](https://lezer.codemirror.net/docs/ref/#markdown.MarkdownConfig)
+
+---
+
+### Step 2 — Create a decoration rule (`inline-rules.ts` or `block-rules.ts`)
+
+Build a factory function that matches your Lezer node type and returns `DecorationRange` objects:
+
+```typescript
+// inline-rules.ts
+function createFooRule(): MarkdownRule<unknown> {
   return {
-    name: "my-rule",
-    // Lezer node type(s) this rule targets
-    nodeTypes: ["MyNode"],
+    name: "foo",
+    nodeTypes: ["FooMarker"],
     match(node) {
-      return node.type.name === "MyNode"
+      return node.type.name === "FooMarker"
     },
     run(node, ctx) {
-      return [{
-        type: "content",
-        from: node.from,
-        to: node.to,
-        className: "my-decoration",
-      }]
+      // Return two ranges: hidden delimiters + visible content
+      return [
+        { type: "hidden", from: node.from, to: node.from + 2, className: "md-hidden" },
+        { type: "content", from: node.from + 2, to: node.to - 2, className: "md-foo" },
+        { type: "hidden", from: node.to - 2, to: node.to, className: "md-hidden" },
+      ]
     },
   }
 }
 ```
 
-**3. Register it in the `MarkdownRuleEngine` constructor** in `engine.ts`.
+For block-level syntax, create a `BlockRule` instead (see `createHeadingRule`, `createTableRule` in `block-rules.ts`).
+
+---
+
+### Step 3 — Register the rule
+
+**For external/plugin code** (recommended — no source changes needed):
+
+```typescript
+import { MarkdownRuleEngine } from "@enesis/editor"
+
+const engine = new MarkdownRuleEngine({
+  extraInlineRules: [createFooRule()],
+  // extraBlockRules: [createMyBlockRule()],
+})
+```
+
+**For built-in syntax** (modify `engine.ts`):
 
 ```typescript
-constructor() {
-  this.inlineRules = [
-    ...createInlineRules(),
-    createMyInlineRule(), // ← add here
-  ]
-}
+// engine.ts constructor
+this.inlineRules = [
+  ...createInlineRules(),
+  createFooRule(), // ← add here
+]
 ```
 
-**4. Write a mirror test** in `packages/editor/src/lib/__tests__/markdown-parser.test.ts`. Follow the existing pattern:
+---
+
+### Step 4 — Write a test
+
+Add a test in `packages/editor/src/lib/__tests__/markdown-parser.test.ts`:
 
 ```typescript
-describe("MyInlineRule", () => {
-  it("decorates correctly", () => {
-    const result = engine.parse("some ~example~ text")
+describe("FooRule", () => {
+  it("decorates foo content correctly", () => {
+    const result = engine.parse("hello ::world:: foo")
     expect(result.content).toContainEqual({
       type: "content",
-      from: 5,
-      to: 12,
-      className: "my-decoration",
+      from: 8,
+      to: 13,
+      className: "md-foo",
     })
   })
 })
 ```
 
-**5. Verify cursor behavior.** For any rule with `hidden` ranges (e.g. delimiters that hide when cursor is inside): confirm that `from`/`to` offsets are computed relative to the block's document offset, not the raw fragment string. Check the existing `createDelimitedRule` implementation as the reference.
+---
+
+### Step 5 — Cursor behavior
+
+For any rule with `hidden` ranges: confirm that `from`/`to` offsets are computed relative to the block's document offset, not the raw fragment string. Check `createDelimitedRule` as the reference.
+
+---
 
-**6. Run checks.**
+### Step 6 — Run checks
 
 ```bash
 biome check packages/editor/src/lib/markdown-rules/