Просмотр исходного кода

feat(dev): rebuild as Nuxt UI docs-style site with live examples

- Replace EditorView/MathTestView with 10-page docs site (Overview, Basic Editing, Inline Marks, Tasks & Priorities, Blockquotes & Callouts, Links, Page Refs & Tags, Code Blocks, Properties & Dates, Math)
- Add LiveExample wrapper component for editable demos with source view
- Convert sidebar to UNavigationMenu with vertical orientation
- Add max-width container with side margins
- Remove old EditorView and MathTestView pages
- Fix blockquote decoration type to allow "hidden" marker visibility
Zander Hawke 1 неделя назад
Родитель
Сommit
01fc9292bb

+ 34 - 40
apps/dev/src/App.vue

@@ -1,54 +1,48 @@
+<script setup lang="ts">
+const navigationItems = [
+  { label: "Overview", icon: "i-lucide-book-open", to: "/" },
+  { label: "Basic Editing", icon: "i-lucide-type", to: "/basic-editing" },
+  { label: "Inline Marks", icon: "i-lucide-bold", to: "/inline-marks" },
+  { label: "Tasks & Priorities", icon: "i-lucide-check-square", to: "/tasks" },
+  {
+    label: "Blockquotes & Callouts",
+    icon: "i-lucide-quote",
+    to: "/blockquotes",
+  },
+  { label: "Links", icon: "i-lucide-link", to: "/links" },
+  { label: "Page Refs & Tags", icon: "i-lucide-hash", to: "/refs-tags" },
+  { label: "Code Blocks", icon: "i-lucide-code", to: "/code-blocks" },
+  { label: "Properties & Dates", icon: "i-lucide-list", to: "/properties" },
+  { label: "Math", icon: "i-lucide-sigma", to: "/math" },
+]
+</script>
+
 <template>
   <UApp>
-    <UHeader :toggle="false">
+    <UHeader :ui="{ wrapper: 'border-b border-default' }">
       <template #left>
-        <AppLogo class="w-auto h-6 shrink-0" />
+        <AppLogo class="w-auto h-5 shrink-0" />
       </template>
 
       <template #right>
-        <UButton to="/" color="neutral" variant="ghost">
-          Editor
-        </UButton>
-        <UButton to="/math" color="neutral" variant="ghost">
-          Math Test
-        </UButton>
-
         <UColorModeButton />
-
-        <UButton
-          to="https://codeberg.org/control/editor"
-          target="_blank"
-          icon="i-simple-icons:codeberg"
-          aria-label="codeberg.org"
-          color="neutral"
-          variant="ghost"
-        />
       </template>
     </UHeader>
 
     <UMain>
-      <RouterView />
-    </UMain>
-
-    <USeparator icon="i-simple-icons:nuxtdotjs" />
+      <UPage class="max-w-7xl mx-auto w-full px-6">
+        <template #left>
+          <UPageAside>
+            <UNavigationMenu
+              orientation="vertical"
+              highlight
+              :items="navigationItems"
+            />
+          </UPageAside>
+        </template>
 
-    <UFooter>
-      <template #left>
-        <p class="text-sm text-muted">
-          Built with Nuxt UI • © {{ new Date().getFullYear() }}
-        </p>
-      </template>
-
-      <template #right>
-        <UButton
-          to="https://codeberg.org/control/editor"
-          target="_blank"
-          icon="i-simple-icons:codeberg"
-          aria-label="codeberg.org"
-          color="neutral"
-          variant="ghost"
-        />
-      </template>
-    </UFooter>
+        <RouterView />
+      </UPage>
+    </UMain>
   </UApp>
 </template>

+ 54 - 0
apps/dev/src/components/LiveExample.vue

@@ -0,0 +1,54 @@
+<script setup lang="ts">
+import { Block } from "@enesis/editor"
+import { ref } from "vue"
+import type { MarkerVisibilityMode } from "@/composables/useMarkdownDecorations"
+
+const props = withDefaults(
+  defineProps<{
+    content: string
+    template?: string
+    title?: string
+    description?: string
+    markerMode?: MarkerVisibilityMode
+  }>(),
+  {
+    markerMode: "always-visible" as MarkerVisibilityMode,
+  },
+)
+
+const content = ref(props.content)
+</script>
+
+<template>
+  <div class="space-y-3">
+    <div v-if="title" class="mb-1">
+      <h3 class="text-sm font-semibold text-highlighted">{{ title }}</h3>
+      <p v-if="description" class="text-sm text-muted mt-0.5">{{ description }}</p>
+    </div>
+
+    <div class="rounded-lg border border-default bg-elevated overflow-hidden">
+      <div class="p-4">
+        <Block
+          v-model:content="content"
+          :focused="true"
+          cursor-position="end"
+          :marker-mode="markerMode"
+        />
+      </div>
+
+      <details
+        class="group border-t border-default cursor-pointer select-none"
+      >
+        <summary
+          class="px-4 py-2 text-xs font-medium text-muted hover:text-highlighted transition-colors flex items-center gap-1.5"
+        >
+          <span class="i-lucide-code shrink-0 size-3.5" />
+          Source
+        </summary>
+        <pre
+          class="px-4 py-3 overflow-x-auto text-xs leading-relaxed font-mono bg-elevated/50 border-t border-default text-muted"
+        ><code>{{ template ?? content }}</code></pre>
+      </details>
+    </div>
+  </div>
+</template>

+ 20 - 4
apps/dev/src/main.ts

@@ -2,13 +2,29 @@ import ui from "@nuxt/ui/vue-plugin"
 import { createApp } from "vue"
 import { createRouter, createWebHistory } from "vue-router"
 import App from "~/App.vue"
-import EditorView from "~/pages/EditorView.vue"
-import MathTestView from "~/pages/MathTestView.vue"
+import BasicEditing from "~/pages/basic-editing.vue"
+import Blockquotes from "~/pages/blockquotes.vue"
+import CodeBlocks from "~/pages/code-blocks.vue"
+import IndexPage from "~/pages/index.vue"
+import InlineMarks from "~/pages/inline-marks.vue"
+import Links from "~/pages/links.vue"
+import MathPage from "~/pages/math.vue"
+import Properties from "~/pages/properties.vue"
+import RefsTags from "~/pages/refs-tags.vue"
+import Tasks from "~/pages/tasks.vue"
 import "~/style.css"
 
 const routes = [
-  { path: "/", component: EditorView },
-  { path: "/math", component: MathTestView },
+  { path: "/", component: IndexPage },
+  { path: "/basic-editing", component: BasicEditing },
+  { path: "/inline-marks", component: InlineMarks },
+  { path: "/tasks", component: Tasks },
+  { path: "/blockquotes", component: Blockquotes },
+  { path: "/links", component: Links },
+  { path: "/refs-tags", component: RefsTags },
+  { path: "/code-blocks", component: CodeBlocks },
+  { path: "/properties", component: Properties },
+  { path: "/math", component: MathPage },
 ]
 
 const app = createApp(App)

+ 0 - 127
apps/dev/src/pages/EditorView.vue

@@ -1,127 +0,0 @@
-<script setup lang="ts">
-import { Block } from "@enesis/editor"
-import { ref } from "vue"
-
-const content = ref(`# Welcome
-This is a **block editor** with markdown decorations.
-
-> [!NOTE] This is a callout block with important information.
-> This is a continuation of the callout -^
-
-[[Page Name]] creates a page reference chip, while ((block-id)) creates a block reference.
-
-#tag creates a tag chip, and you can use links like [this link](https://example.com).
-
-📅 2026-05-20 is a date
-
-\`\`\`python
-def hello():
-    print("Hello from CM6!")
-    for i in range(3):
-        print(f"Line {i}")
-\`\`\`
-
-property:: This is a property line
-another:: [[Page reference]] in a property
-`)
-
-const task1 = ref(`# Test callout
-> [!NOTE] First line of the callout
-> second line continuation
-`)
-const task2 = ref("DOING this is another task")
-const task3 = ref("DONE a completed task [#A]")
-
-function log(event: string, payload?: unknown) {
-  console.log(`[Block] ${event}`, payload)
-
-  if (event === "blur") {
-    console.log(content.value)
-  }
-}
-</script>
-
-<template>
-  <div class="p-8 max-w-2xl mx-auto">
-    <Block
-      v-model:content="content"
-      :focused="true"
-      cursor-position="end"
-      marker-mode="always-visible"
-      @change="log('change', $event)"
-      @split="log('split', $event)"
-      @merge-previous="log('merge-previous')"
-      @delete-if-empty="log('delete-if-empty')"
-      @indent="log('indent')"
-      @outdent="log('outdent')"
-      @arrow-up-from-start="log('arrow-up-from-start')"
-      @arrow-down-from-end="log('arrow-down-from-end')"
-      @pattern-open="log('pattern-open', $event)"
-      @pattern-update="log('pattern-update', $event)"
-      @pattern-close="log('pattern-close', $event)"
-      @focus="log('focus', $event)"
-      @blur="log('blur')"
-      @selection-change="log('selection-change', $event)"
-    />
-
-    <Block
-      v-model:content="task1"
-      :focused="true"
-      cursor-position="end"
-      @change="log('change', $event)"
-      @split="log('split', $event)"
-      @merge-previous="log('merge-previous')"
-      @delete-if-empty="log('delete-if-empty')"
-      @indent="log('indent')"
-      @outdent="log('outdent')"
-      @arrow-up-from-start="log('arrow-up-from-start')"
-      @arrow-down-from-end="log('arrow-down-from-end')"
-      @pattern-open="log('pattern-open', $event)"
-      @pattern-update="log('pattern-update', $event)"
-      @pattern-close="log('pattern-close', $event)"
-      @focus="log('focus', $event)"
-      @blur="log('blur')"
-      @selection-change="log('selection-change', $event)"
-    />
-
-    <Block
-      v-model:content="task2"
-      :focused="true"
-      cursor-position="end"
-      @change="log('change', $event)"
-      @split="log('split', $event)"
-      @merge-previous="log('merge-previous')"
-      @delete-if-empty="log('delete-if-empty')"
-      @indent="log('indent')"
-      @outdent="log('outdent')"
-      @arrow-up-from-start="log('arrow-up-from-start')"
-      @arrow-down-from-end="log('arrow-down-from-end')"
-      @pattern-open="log('pattern-open', $event)"
-      @pattern-update="log('pattern-update', $event)"
-      @pattern-close="log('pattern-close', $event)"
-      @focus="log('focus', $event)"
-      @blur="log('blur')"
-      @selection-change="log('selection-change', $event)"
-    />
-
-    <Block
-      v-model:content="task3"
-      :focused="true"
-      cursor-position="end"
-      @change="log('change', $event)"
-      @split="log('split', $event)"
-      @merge-previous="log('merge-previous')"
-      @delete-if-empty="log('delete-if-empty')"
-      @indent="log('indent')"
-      @outdent="log('outdent')"
-      @arrow-up-from-start="log('arrow-up-from-start')"
-      @arrow-down-from-end="log('arrow-down-from-end')"
-      @pattern-open="log('pattern-open', $event)"
-      @pattern-update="log('pattern-update', $event)"
-      @pattern-close="log('pattern-close', $event)"
-      @focus="log('focus', $event)"
-      @blur="log('blur')"
-      @selection-change="log('selection-change', $event)"
-    />
-  </div>
-</template>

+ 0 - 116
apps/dev/src/pages/MathTestView.vue

@@ -1,116 +0,0 @@
-<script setup lang="ts">
-import { Block } from "@enesis/editor"
-import { ref } from "vue"
-
-const inlineMath = ref(`This has inline math: $x^2 + y^2 = z^2$ inside a paragraph.
-
-You can also do $\\sum_{i=1}^n i = \\frac{n(n+1)}{2}$ for summations.
-
-And $\\alpha\\beta\\gamma$ for Greek letters.
-
-A bare $5 should not render as math. Neither should $ this$.`)
-
-const blockMath = ref(`Here is some block math:
-
-$$
-\\int_{-\\infty}^{\\infty} e^{-x^2} \\, dx = \\sqrt{\\pi}
-$$
-
-And another one:
-
-$$
-\\begin{bmatrix}
-a & b \\\\
-c & d
-\\end{bmatrix}
-$$`)
-
-const mixed = ref(`# Math Test Document
-
-You can mix **bold** and $x$ math inline.
-
-> [!NOTE] Math in callouts works: $E = mc^2$
-
-[[Page Ref]] with $\\theta$ math.
-
-TODO Test task with math: $\\Delta$ [#A]
-
-$$
-f(x) = \\begin{cases}
-x^2 & \\text{if } x > 0 \\\\
-0 & \\text{otherwise}
-\\end{cases}
-$$
-
-property:: math expression $\\lambda$ test`)
-
-function log(event: string, payload?: unknown) {
-  console.log(`[Block] ${event}`, payload)
-
-  if (event === "blur") {
-    console.log("Content:", inlineMath.value, blockMath.value, mixed.value)
-  }
-}
-</script>
-
-<template>
-  <div class="p-8 max-w-2xl mx-auto space-y-6">
-    <h1 class="text-2xl font-bold">Math Feature Tests</h1>
-
-    <section class="rounded-lg border border-default p-4 space-y-2">
-      <h2 class="text-lg font-semibold">Inline Math</h2>
-      <p class="text-sm text-muted">
-        Type <code class="text-xs">$...$</code> for inline math. Use
-        <kbd class="px-1.5 py-0.5 rounded bg-elevated border border-default text-xs">Mod+M</kbd>
-        to toggle inline math on selection.
-      </p>
-      <Block
-        v-model:content="inlineMath"
-        :focused="true"
-        cursor-position="end"
-        marker-mode="always-visible"
-        @change="log('change', $event)"
-        @focus="log('focus', $event)"
-        @blur="log('blur')"
-        @selection-change="log('selection-change', $event)"
-      />
-    </section>
-
-    <section class="rounded-lg border border-default p-4 space-y-2">
-      <h2 class="text-lg font-semibold">Block Math</h2>
-      <p class="text-sm text-muted">
-        Type <code class="text-xs">$$</code> at the start of an empty paragraph for a
-        display math block. Use
-        <kbd class="px-1.5 py-0.5 rounded bg-elevated border border-default text-xs">Mod+Shift+M</kbd>
-        to insert one.
-      </p>
-      <Block
-        v-model:content="blockMath"
-        :focused="true"
-        cursor-position="end"
-        marker-mode="always-visible"
-        @change="log('change', $event)"
-        @focus="log('focus', $event)"
-        @blur="log('blur')"
-        @selection-change="log('selection-change', $event)"
-      />
-    </section>
-
-    <section class="rounded-lg border border-default p-4 space-y-2">
-      <h2 class="text-lg font-semibold">Mixed Syntax</h2>
-      <p class="text-sm text-muted">
-        Math combined with headings, bold, callouts, page refs, tasks, and properties.
-      </p>
-      <Block
-        v-model:content="mixed"
-        :focused="true"
-        cursor-position="end"
-        marker-mode="live-preview"
-        @change="log('change', $event)"
-        @focus="log('focus', $event)"
-        @blur="log('blur')"
-        @selection-change="log('selection-change', $event)"
-      />
-    </section>
-  </div>
-</template>

+ 31 - 0
apps/dev/src/pages/basic-editing.vue

@@ -0,0 +1,31 @@
+<script setup lang="ts">
+import LiveExample from "~/components/LiveExample.vue"
+</script>
+
+<template>
+  <UPage>
+    <UPageHeader title="Basic Editing" description="Headings, paragraphs, and structure." />
+
+    <UPageBody>
+      <div class="space-y-8">
+        <LiveExample
+          title="Headings"
+          description="Use 1-6 # characters followed by a space."
+          :content="`# Heading 1\n\n## Heading 2\n\n### Heading 3\n\n#### Heading 4`"
+        />
+
+        <LiveExample
+          title="Paragraphs"
+          description="Blank lines separate paragraphs. Each paragraph is its own Block."
+          :content="`First paragraph with some text.\n\nSecond paragraph goes here.\n\nThird paragraph with more content for testing.`"
+        />
+
+        <LiveExample
+          title="Horizontal Rule"
+          description="Three or more dashes create a horizontal rule."
+          :content="`Content above the rule\n\n---\n\nContent below the rule`"
+        />
+      </div>
+    </UPageBody>
+  </UPage>
+</template>

+ 49 - 0
apps/dev/src/pages/blockquotes.vue

@@ -0,0 +1,49 @@
+<script setup lang="ts">
+import LiveExample from "~/components/LiveExample.vue"
+</script>
+
+<template>
+  <UPage>
+    <UPageHeader title="Blockquotes & Callouts" description="Blockquote syntax with optional callout types." />
+
+    <UPageBody>
+      <div class="space-y-8">
+        <LiveExample
+          title="Blockquote"
+          description="Start a line with > followed by a space."
+          :content="`> This is a blockquote.\n> It spans multiple lines.\n> Each line starts with >.`"
+        />
+
+        <LiveExample
+          title="Callouts"
+          description="Use > [!TYPE] to create a styled callout. Valid types: NOTE, WARNING, TIP, DANGER, INFO."
+          :content="`> [!NOTE] This is a note callout\n> Continuation of the note`"
+        />
+
+        <LiveExample
+          title="Warning Callout"
+          description="Warning callouts have a yellow accent."
+          :content="`> [!WARNING] Check your configuration\n> This setting may affect performance.`"
+        />
+
+        <LiveExample
+          title="Tip Callout"
+          description="Tip callouts have a green accent."
+          :content="`> [!TIP] Use keyboard shortcuts\n> Mod+M toggles inline math.`"
+        />
+
+        <LiveExample
+          title="Danger Callout"
+          description="Danger callouts have a red accent."
+          :content="`> [!DANGER] This is destructive\n> This action cannot be undone.`"
+        />
+
+        <LiveExample
+          title="Info Callout"
+          description="Info callouts use the primary color."
+          :content="`> [!INFO] The editor uses ProseMirror\n> Each Block is an independent editor instance.`"
+        />
+      </div>
+    </UPageBody>
+  </UPage>
+</template>

+ 80 - 0
apps/dev/src/pages/code-blocks.vue

@@ -0,0 +1,80 @@
+<script setup lang="ts">
+import LiveExample from "~/components/LiveExample.vue"
+
+const basicCode = `Here is some code:
+
+\`\`\`
+const x = 42
+console.log(x)
+\`\`\``
+
+const languageDetection = `A Python example:
+
+\`\`\`python
+def hello():
+    print("Hello world!")
+    for i in range(3):
+        print(f"Line {i}")
+\`\`\``
+
+const tsCode = `TypeScript example:
+
+\`\`\`typescript
+interface User {
+  name: string
+  age: number
+}
+
+const user: User = {
+  name: "Alice",
+  age: 30
+}
+\`\`\``
+
+const cssCode = `\`\`\`css
+.container {
+  display: flex;
+  gap: 1rem;
+  padding: 2rem;
+}
+
+.title {
+  font-size: 1.5rem;
+  font-weight: 600;
+}
+\`\`\``
+</script>
+
+<template>
+  <UPage>
+    <UPageHeader title="Code Blocks" description="Fenced code blocks with syntax highlighting." />
+
+    <UPageBody>
+      <div class="space-y-8">
+        <LiveExample
+          title="Basic Code Block"
+          description="Use triple backticks ``` to create a code block."
+          :content="basicCode"
+        />
+
+        <LiveExample
+          title="Language Detection"
+          description="Specify a language after the opening backticks for syntax highlighting."
+          :content="languageDetection"
+        />
+
+        <LiveExample
+          title="TypeScript"
+          description="TypeScript with proper syntax highlighting."
+          :content="tsCode"
+        />
+
+        <LiveExample
+          title="CSS"
+          description="CSS with syntax highlighting."
+          :content="cssCode"
+        />
+      </div>
+    </UPageBody>
+  </UPage>
+</template>

+ 136 - 0
apps/dev/src/pages/index.vue

@@ -0,0 +1,136 @@
+<script setup lang="ts">
+import { Block } from "@enesis/editor"
+import { ref } from "vue"
+
+const example = ref(`# Welcome to the Editor
+
+This is a **block-based markdown editor** built with Vue 3, TypeScript, and ProseMirror.
+
+> [!NOTE] Every paragraph is its own Block component.
+> Blocks are independent editors with their own state.
+
+Try editing any of the examples on the left.
+`)
+</script>
+
+<template>
+  <UPage>
+    <UPageHeader
+      title="Editor"
+      description="A block-based markdown editor with live preview, decorations, and extensible syntax."
+    />
+
+    <UPageBody>
+      <div class="space-y-8">
+        <section class="space-y-4">
+          <h2 class="text-lg font-semibold text-highlighted">What is this?</h2>
+          <p class="text-sm text-muted leading-relaxed">
+            The editor is a headless engine — each <code class="text-xs font-mono px-1 py-0.5 rounded bg-elevated border border-default">Block</code> component manages a single paragraph with its own ProseMirror instance.
+            Markdown syntax is rendered as rich decorations in live-preview mode: delimiters fade when blurred, syntax elements get contextual styling.
+          </p>
+          <div class="rounded-lg border border-default bg-elevated p-4">
+            <Block
+              v-model:content="example"
+              :focused="true"
+              cursor-position="end"
+              marker-mode="always-visible"
+            />
+          </div>
+        </section>
+
+        <section class="space-y-4">
+          <h2 class="text-lg font-semibold text-highlighted">Key Concepts</h2>
+          <div class="grid gap-4 sm:grid-cols-2">
+            <div class="rounded-lg border border-default p-4 space-y-2">
+              <div class="flex items-center gap-2">
+                <span class="i-lucide-layers size-4 text-primary shrink-0" />
+                <h3 class="text-sm font-medium text-highlighted">Blocks</h3>
+              </div>
+              <p class="text-xs text-muted leading-relaxed">
+                Documents are split into distinct Block components — each one is an independent editor.
+                Blocks communicate through a shared document model.
+              </p>
+            </div>
+            <div class="rounded-lg border border-default p-4 space-y-2">
+              <div class="flex items-center gap-2">
+                <span class="i-lucide-palette size-4 text-primary shrink-0" />
+                <h3 class="text-sm font-medium text-highlighted">Decorations</h3>
+              </div>
+              <p class="text-xs text-muted leading-relaxed">
+                Markdown syntax is rendered as ProseMirror decorations — styled spans overlaid on the source text.
+                Delimiters animate in and out based on focus.
+              </p>
+            </div>
+            <div class="rounded-lg border border-default p-4 space-y-2">
+              <div class="flex items-center gap-2">
+                <span class="i-lucide-parse size-4 text-primary shrink-0" />
+                <h3 class="text-sm font-medium text-highlighted">Parsing Pipeline</h3>
+              </div>
+              <p class="text-xs text-muted leading-relaxed">
+                A 3-stage pipeline classifies blocks, builds a Lezer AST, then extracts decorations.
+                Everything runs in under 16ms per keystroke.
+              </p>
+            </div>
+            <div class="rounded-lg border border-default p-4 space-y-2">
+              <div class="flex items-center gap-2">
+                <span class="i-lucide-plug size-4 text-primary shrink-0" />
+                <h3 class="text-sm font-medium text-highlighted">Extensible</h3>
+              </div>
+              <p class="text-xs text-muted leading-relaxed">
+                Inline and block rules are registered as factories. Add new syntax by creating a rule, registering it, and writing a test.
+              </p>
+            </div>
+          </div>
+        </section>
+
+        <section class="space-y-4">
+          <h2 class="text-lg font-semibold text-highlighted">Block API</h2>
+
+          <div class="rounded-lg border border-default overflow-hidden">
+            <table class="w-full text-sm">
+              <thead>
+                <tr class="border-b border-default bg-elevated">
+                  <th class="text-left px-4 py-2 font-medium text-muted text-xs uppercase tracking-wider">Prop</th>
+                  <th class="text-left px-4 py-2 font-medium text-muted text-xs uppercase tracking-wider">Type</th>
+                  <th class="text-left px-4 py-2 font-medium text-muted text-xs uppercase tracking-wider">Default</th>
+                  <th class="text-left px-4 py-2 font-medium text-muted text-xs uppercase tracking-wider">Description</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr class="border-b border-default"><td class="px-4 py-2 font-mono text-xs">content</td><td class="px-4 py-2 text-xs">string</td><td class="px-4 py-2 text-xs">—</td><td class="px-4 py-2 text-xs text-muted">Markdown content (v-model)</td></tr>
+                <tr class="border-b border-default"><td class="px-4 py-2 font-mono text-xs">focused</td><td class="px-4 py-2 text-xs">boolean</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-xs text-muted">Automatically focus the editor</td></tr>
+                <tr class="border-b border-default"><td class="px-4 py-2 font-mono text-xs">cursorPosition</td><td class="px-4 py-2 text-xs">'start' | 'end'</td><td class="px-4 py-2 text-xs">—</td><td class="px-4 py-2 text-xs text-muted">Where to place cursor on focus</td></tr>
+                <tr class="border-b border-default"><td class="px-4 py-2 font-mono text-xs">markerMode</td><td class="px-4 py-2 text-xs">'live-preview' | 'always-visible'</td><td class="px-4 py-2 text-xs">'always-visible'</td><td class="px-4 py-2 text-xs text-muted">How decorator markers behave</td></tr>
+                <tr class="border-b border-default"><td class="px-4 py-2 font-mono text-xs">debug</td><td class="px-4 py-2 text-xs">string</td><td class="px-4 py-2 text-xs">—</td><td class="px-4 py-2 text-xs text-muted">Debug namespace filter</td></tr>
+              </tbody>
+            </table>
+          </div>
+        </section>
+
+        <section class="space-y-4">
+          <h2 class="text-lg font-semibold text-highlighted">Events</h2>
+          <div class="rounded-lg border border-default overflow-hidden">
+            <table class="w-full text-sm">
+              <thead>
+                <tr class="border-b border-default bg-elevated">
+                  <th class="text-left px-4 py-2 font-medium text-muted text-xs uppercase tracking-wider">Event</th>
+                  <th class="text-left px-4 py-2 font-medium text-muted text-xs uppercase tracking-wider">Payload</th>
+                  <th class="text-left px-4 py-2 font-medium text-muted text-xs uppercase tracking-wider">Description</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr class="border-b border-default"><td class="px-4 py-2 font-mono text-xs">change</td><td class="px-4 py-2 text-xs">string</td><td class="px-4 py-2 text-xs text-muted">Content changed</td></tr>
+                <tr class="border-b border-default"><td class="px-4 py-2 font-mono text-xs">focus</td><td class="px-4 py-2 text-xs">void</td><td class="px-4 py-2 text-xs text-muted">Editor gained focus</td></tr>
+                <tr class="border-b border-default"><td class="px-4 py-2 font-mono text-xs">blur</td><td class="px-4 py-2 text-xs">void</td><td class="px-4 py-2 text-xs text-muted">Editor lost focus</td></tr>
+                <tr class="border-b border-default"><td class="px-4 py-2 font-mono text-xs">selection-change</td><td class="px-4 py-2 text-xs">{ from, to }</td><td class="px-4 py-2 text-xs text-muted">Cursor/selection moved</td></tr>
+                <tr class="border-b border-default"><td class="px-4 py-2 font-mono text-xs">split</td><td class="px-4 py-2 text-xs">string</td><td class="px-4 py-2 text-xs text-muted">Block split on Enter</td></tr>
+                <tr class="border-b border-default"><td class="px-4 py-2 font-mono text-xs">merge-previous</td><td class="px-4 py-2 text-xs">void</td><td class="px-4 py-2 text-xs text-muted">Backspace at start merges blocks</td></tr>
+                <tr class="border-b border-default"><td class="px-4 py-2 font-mono text-xs">delete-if-empty</td><td class="px-4 py-2 text-xs">void</td><td class="px-4 py-2 text-xs text-muted">Backspace on empty block</td></tr>
+              </tbody>
+            </table>
+          </div>
+        </section>
+      </div>
+    </UPageBody>
+  </UPage>
+</template>

+ 49 - 0
apps/dev/src/pages/inline-marks.vue

@@ -0,0 +1,49 @@
+<script setup lang="ts">
+import LiveExample from "~/components/LiveExample.vue"
+</script>
+
+<template>
+  <UPage>
+    <UPageHeader title="Inline Marks" description="Bold, italic, code, strikethrough, and highlight." />
+
+    <UPageBody>
+      <div class="space-y-8">
+        <LiveExample
+          title="Bold"
+          description="Wrap text in **double asterisks**."
+          :content="`This is **bold text** and this is also **bold** inline.`"
+        />
+
+        <LiveExample
+          title="Italic"
+          description="Wrap text in *single asterisks*."
+          :content="`This is *italic text* and this is also *italic* inline.`"
+        />
+
+        <LiveExample
+          title="Inline Code"
+          description="Wrap text in \`backticks\`."
+          :content="`Use the \`const\` keyword or call \`console.log()\` to debug.`"
+        />
+
+        <LiveExample
+          title="Strikethrough"
+          description="Wrap text in ~~double tildes~~."
+          :content="`This is ~~strikethrough text~~ that has been removed.`"
+        />
+
+        <LiveExample
+          title="Highlight"
+          description="Wrap text in ^^double carets^^."
+          :content="`This is ^^highlighted text^^ that stands out.`"
+        />
+
+        <LiveExample
+          title="Combined Marks"
+          description="Marks can be nested inside each other."
+          :content="`This is **bold with *italic* inside** and \`code with ~~strike~~\`.`"
+        />
+      </div>
+    </UPageBody>
+  </UPage>
+</template>

+ 31 - 0
apps/dev/src/pages/links.vue

@@ -0,0 +1,31 @@
+<script setup lang="ts">
+import LiveExample from "~/components/LiveExample.vue"
+</script>
+
+<template>
+  <UPage>
+    <UPageHeader title="Links" description="Standard markdown links with inline formatting." />
+
+    <UPageBody>
+      <div class="space-y-8">
+        <LiveExample
+          title="Basic Link"
+          description="Use [text](url) syntax."
+          :content="`Visit [Example](https://example.com) for more information.`"
+        />
+
+        <LiveExample
+          title="Links with Bold"
+          description="Link text can contain formatting."
+          :content="`Check out [**important link**](https://example.com) for details.`"
+        />
+
+        <LiveExample
+          title="Multiple Links"
+          description="Multiple links in the same paragraph."
+          :content="`Search on [Google](https://google.com) or [DuckDuckGo](https://duckduckgo.com).`"
+        />
+      </div>
+    </UPageBody>
+  </UPage>
+</template>

+ 74 - 0
apps/dev/src/pages/math.vue

@@ -0,0 +1,74 @@
+<script setup lang="ts">
+import LiveExample from "~/components/LiveExample.vue"
+</script>
+
+<template>
+  <UPage>
+    <UPageHeader title="Math" description="Inline $...$ and display $$...$$ math with KaTeX rendering." />
+
+    <UPageBody>
+      <div class="space-y-8">
+        <section>
+          <h2 class="text-lg font-semibold text-highlighted mb-3">Inline Math</h2>
+          <div class="space-y-6">
+            <LiveExample
+              title="Basic Inline"
+              description="Use single $ delimiters for inline math."
+              :content="`The formula $E = mc^2$ is famous.`"
+            />
+
+            <LiveExample
+              title="Complex Expressions"
+              description="Inline math handles complex LaTeX."
+              :content="`The sum $\\sum_{i=1}^n i = \\frac{n(n+1)}{2}$ converges nicely.`"
+            />
+
+            <LiveExample
+              title="Greek Letters"
+              description="Standard LaTeX Greek letters work inline."
+              :content="`Angles: $\\alpha$, $\\beta$, $\\gamma$ are common variables.`"
+            />
+          </div>
+        </section>
+
+        <USeparator />
+
+        <section>
+          <h2 class="text-lg font-semibold text-highlighted mb-3">Block Math</h2>
+          <div class="space-y-6">
+            <LiveExample
+              title="Display Math"
+              description="Use $$...$$ for display math blocks. Type $$ at the start of an empty paragraph."
+              :content="`Here is a displayed equation:\n\n$$\n\\int_{-\\infty}^{\\infty} e^{-x^2} \\, dx = \\sqrt{\\pi}\n$$`"
+            />
+
+            <LiveExample
+              title="Matrices"
+              description="Block math supports matrices and arrays."
+              :content="`A 2x2 matrix:\n\n$$\n\\begin{bmatrix}\na & b \\\\\nc & d\n\\end{bmatrix}\n$$`"
+            />
+
+            <LiveExample
+              title="Piecewise Functions"
+              description="Block math handles cases and piecewise definitions."
+              :content="`Piecewise function:\n\n$$\nf(x) = \\begin{cases}\nx^2 & \\text{if } x > 0 \\\\\n0 & \\text{otherwise}\n\\end{cases}\n$$`"
+            />
+          </div>
+        </section>
+
+        <USeparator />
+
+        <section>
+          <h2 class="text-lg font-semibold text-highlighted mb-3">Mixed Content</h2>
+          <div class="space-y-6">
+            <LiveExample
+              title="Math with Other Syntax"
+              description="Math works alongside formatting, callouts, refs, and tasks."
+              :content="`## Math Document\n\nYou can use **bold** and $x$ inline math together.\n\n> [!NOTE] Callouts with math: $E = mc^2$\n\n[[Physics Notes]] with $\\theta$ angle.\n\nTODO Review $\\Delta$ formula [#A]`"
+            />
+          </div>
+        </section>
+      </div>
+    </UPageBody>
+  </UPage>
+</template>

+ 31 - 0
apps/dev/src/pages/properties.vue

@@ -0,0 +1,31 @@
+<script setup lang="ts">
+import LiveExample from "~/components/LiveExample.vue"
+</script>
+
+<template>
+  <UPage>
+    <UPageHeader title="Properties & Dates" description="Key-value metadata and date parsing." />
+
+    <UPageBody>
+      <div class="space-y-8">
+        <LiveExample
+          title="Properties"
+          description="Use key:: value syntax at the end of a line."
+          :content="`This is a document with metadata.\nauthor:: John Doe\nstatus:: published`"
+        />
+
+        <LiveExample
+          title="Dates"
+          description="ISO dates are detected and styled."
+          :content="`📅 2026-06-14 is a recognized date\n📅 2025-12-25 Christmas`"
+        />
+
+        <LiveExample
+          title="Mixed Properties"
+          description="Properties work alongside other syntax."
+          :content="`## Meeting Notes\ntags:: #development #planning\ndate:: 2026-06-14\nTODO Review the agenda [#A]`"
+        />
+      </div>
+    </UPageBody>
+  </UPage>
+</template>

+ 37 - 0
apps/dev/src/pages/refs-tags.vue

@@ -0,0 +1,37 @@
+<script setup lang="ts">
+import LiveExample from "~/components/LiveExample.vue"
+</script>
+
+<template>
+  <UPage>
+    <UPageHeader title="Page Refs & Tags" description="Wiki-style references and hashtags." />
+
+    <UPageBody>
+      <div class="space-y-8">
+        <LiveExample
+          title="Page References"
+          description="Use [[Page Name]] to create a page ref chip."
+          :content="`See [[Getting Started]] for the tutorial.\n\nReference [[Real Page|Display Name]] to show custom text.`"
+        />
+
+        <LiveExample
+          title="Block References"
+          description="Use ((block-id)) to reference another block."
+          :content="`As mentioned in ((abc123)), the feature is ready.\n\nRefer to ((def456)) for implementation details.`"
+        />
+
+        <LiveExample
+          title="Tags"
+          description="Use #tag to create a tag chip."
+          :content="`This is about #documentation and #development.\n\n#bug #feature #enhancement`"
+        />
+
+        <LiveExample
+          title="Combined References"
+          description="References work with other inline formatting."
+          :content="`The **[[Main Page]]** has been updated with #new features.\n\nSee ((ref-123)) for *details* on #implementation.`"
+        />
+      </div>
+    </UPageBody>
+  </UPage>
+</template>

+ 37 - 0
apps/dev/src/pages/tasks.vue

@@ -0,0 +1,37 @@
+<script setup lang="ts">
+import LiveExample from "~/components/LiveExample.vue"
+</script>
+
+<template>
+  <UPage>
+    <UPageHeader title="Tasks & Priorities" description="Task states with priority levels." />
+
+    <UPageBody>
+      <div class="space-y-8">
+        <LiveExample
+          title="Task States"
+          description="Valid states: TODO, DOING, DONE, LATER, NOW, WAITING, CANCELLED."
+          :content="`TODO Fix the login bug\nDOING Working on the API\nDONE Deployed to production\nLATER Review the PR\nWAITING Need design feedback\nCANCELLED Won't implement`"
+        />
+
+        <LiveExample
+          title="Task Cycle"
+          description="TODO → DOING → DONE is the linear cycle. LATER, NOW, WAITING, CANCELLED are orthogonal."
+          :content="`TODO Write documentation\nDOING Refactor tests\nDONE Ship v1.0`"
+        />
+
+        <LiveExample
+          title="Priorities"
+          description="Append [#A], [#B], or [#C] to set priority."
+          :content="`TODO Fix critical bug [#A]\nDOING Add feature [#B]\nDONE Minor cleanup [#C]`"
+        />
+
+        <LiveExample
+          title="Mixed Content"
+          description="Tasks work with inline formatting."
+          :content="`TODO Write **bold** documentation\nDOING Fix the *really* tricky bug [#A]\nDONE ~~Remove~~ deprecated code [#C]`"
+        />
+      </div>
+    </UPageBody>
+  </UPage>
+</template>

+ 1 - 1
packages/editor/src/lib/markdown-rules/block-rules.ts

@@ -61,7 +61,7 @@ function createPlainBlockquoteDecoration(
   text: string,
 ): BlockDecoration {
   const decorationRanges: {
-    type: "content"
+    type: "hidden" | "content"
     from: number
     to: number
     className: string