You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/eidos/components/words/words-drawer.svelte

513 lines
16 KiB

feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
<script lang="ts">
/**
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
* Words.Drawer — companion right-rail eidos wrapper.
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
*
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
* After HIER-1/2/3 the drawer renders a STACK of panels from
* most-specific to most-general. E.g. inside a table cell with
* text selected the user sees: Format → Cell → Row → Table —
* each panel scoped to its level so every relevant action is
* reachable from one surface (instead of guessing which popover/
* menu owns it).
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
*
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
* Modes are derived in soma (`WordsDrawerProvider.modes`); eidos
* just paints them.
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
*/
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
import { ChevronRight, ChevronLeft, ChevronDown, ChevronUp } from '$uix/eidos/components/icon';
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
import * as Words from '$soma/components/words';
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
import type {
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
DrawerSnippetProps,
DrawerProps,
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
ProviderSnippetProps,
WordsDrawerMode
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
} from '$soma/components/words';
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
import { SvelteMap } from 'svelte/reactivity';
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
let {
open = $bindable(undefined),
defaultOpen = true,
disabled = false,
children: customChildren,
...rest
}: Omit<DrawerProps, 'child'> = $props();
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
const MODE_LABELS: Record<WordsDrawerMode, string> = {
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
default: 'Document',
format: 'Format',
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
block: 'Block',
cell: 'Cell',
row: 'Row',
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
table: 'Table',
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
'list-item': 'List item',
list: 'List',
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
code: 'Code',
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
image: 'Image'
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
};
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
// Per-panel expand/collapse map. Default heuristic: first mode in
// the stack (most specific) is open, the rest collapsed. User
// explicit toggles override the default and persist across context
// changes.
const panelOpenOverrides = new SvelteMap<WordsDrawerMode, boolean>();
function isPanelOpen(mode: WordsDrawerMode, modes: readonly WordsDrawerMode[]): boolean {
const override = panelOpenOverrides.get(mode);
if (override !== undefined) return override;
// Default: only the top of the stack is open.
return modes[0] === mode;
}
function togglePanel(mode: WordsDrawerMode, modes: readonly WordsDrawerMode[]) {
panelOpenOverrides.set(mode, !isPanelOpen(mode, modes));
}
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
function countWordsInText(text: string): number {
const trimmed = text.trim();
return trimmed ? trimmed.split(/\s+/).length : 0;
}
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
function imageBlockAtCaret(s: ProviderSnippetProps):
| {
readonly src: string;
readonly alt?: string;
readonly caption?: string;
readonly status?: 'pending' | 'error';
}
| undefined {
feat(words): drawer contextual panels — format/image/table/code/list (DRAWER-2) The companion drawer now switches its body content based on where the caret is. Five context modes wired to runCommand on the snippet provider, all reading current state to show active highlights: - format: B/I/U/S/code mark toggles (data-active reflects activeMarks), align L/C/R/J (data-active reflects currentTextAlign), Add link (URL prompt) + Remove (canUnlink-gated), Clear formatting. - image: read-only view of src, alt (italic + "(missing)" placeholder when empty), caption, status (pending → accent tint, error → threat tint). Edit-image flow deferred to a follow-up (needs a new updateImage operation; for the spike, view-only is enough to validate the panel works). - table: Rows +/- · Columns +/- · Row/Col header toggles · Striped / Compact toggles · Cell tone Plain/Muted/Accent. Every toggle reads current* state so the user sees which buttons are currently applied. - code: Plain + TS/JS/CSS/HTML/JSON/Bash/Py language chips, data-active reflects currentCodeLanguage. - list: Bullet/Numbered/Check kind chips · ⇤ Outdent / ⇥ Indent. All buttons use onmousedown preventDefault so the editor selection isn't dropped when clicking a drawer action (would otherwise lose the target paragraph / table cell / list item). CSS adds a generic .data-words-drawer-action chip (with hover, active, ghost, full, risk tones) + readonly-text style with pending/error tints. Reused across all panels, so adding new panels later means markup-only, no CSS extension. Verified in browser: - Click table cell → drawer shows TABLE panel with Striped active (matches sample's striped=true) and Plain tone active (matches default cell tone). - Click code block → drawer shows CODE panel with TS chip active (matches sample's language='ts'). - Click list item → drawer shows LIST panel with kind + indent actions. Concept A+D validated visually: ONE surface replaces what would otherwise be bubble menu + slash menu + heading picker + language picker + table popover + image controls + link editor. The contextual switch happens automatically; the user just moves the caret. DRAWER-3 (polish): mobile bottom-sheet layout, A/B toggle in demo controls, image-edit flow — deferred to next session if needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
const blockIndex = s.selection?.anchor.path[0];
if (blockIndex === undefined) return undefined;
const block = s.document.children[blockIndex];
if (!block || block.type !== 'image') return undefined;
return block;
}
const CODE_LANGUAGES = [
{ id: '', label: 'Plain' },
{ id: 'ts', label: 'TS' },
{ id: 'js', label: 'JS' },
{ id: 'css', label: 'CSS' },
{ id: 'html', label: 'HTML' },
{ id: 'json', label: 'JSON' },
{ id: 'bash', label: 'Bash' },
{ id: 'python', label: 'Py' }
] as const;
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
</script>
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
<Words.Drawer bind:open {defaultOpen} {disabled} {...rest}>
{#snippet children(d: DrawerSnippetProps)}
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
{@const { modes, snippet, toggle: toggleDrawer } = d}
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
<header data-words-drawer-header>
<button
type="button"
data-words-drawer-collapse
aria-label={d.open ? 'Collapse drawer' : 'Expand drawer'}
aria-expanded={d.open}
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
onclick={(e) => toggleDrawer(e.currentTarget as HTMLElement)}
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
>
{#if d.open}
<ChevronRight size="sm" decorative />
{:else}
<ChevronLeft size="sm" decorative />
{/if}
</button>
{#if d.open}
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
<span data-words-drawer-title>{MODE_LABELS[modes[0]]}</span>
{#if modes.length > 1}
<span data-words-drawer-trail aria-hidden="true">
{#each modes.slice(1) as m, i (m)}
<span data-words-drawer-trail-sep>·</span>
<span data-words-drawer-trail-item>{MODE_LABELS[m]}</span>
{/each}
</span>
{/if}
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
{/if}
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
</header>
feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1) A radical disposition shift: instead of N fragmented popovers (bubble menu + slash menu + link editor + heading picker + language picker + table popover + image controls) we mount ONE contextual right-rail that changes its panel based on what the user is editing. Spike-1 surface (this commit) ships: - New eidos component <Words.Drawer> (words-drawer.svelte) — a collapsible right sidebar with mode detection derived from snippet props (currentBlock + selection). 6 modes mapped: default | format | image | table | code | list. - Default panel (visible when no contextual mode active): - OUTLINE — flat list of h1/h2/h3 headings, indented by level - STATS — word count + char count + reading time (200wpm) - SUGGESTIONS — writer-coaching warnings derived from the document model: missing alt text, long paragraphs (>600 chars), heading skip patterns (h3 without h2 above). Empty state shows "Nothing flagged" in affirm color. - Contextual panels (format/image/table/code/list) render a placeholder "coming soon" message — implemented in DRAWER-2. - Eidos CSS: - Provider host gains a grid-template-areas layout when a drawer is present: 'toolbar drawer / content drawer / status drawer'. Existing children (toolbar/content/placeholder/status) get explicit grid-area assignments. - Drawer collapses to 2.25rem (just the chevron) when closed. 16rem default open. - Outline items, stats grid, warning list each have their own discrete styling. Demo (+page.svelte): - New `showDrawer` state (default true). - Children snippet refactored to capture full snippetProps via @const destructure (kept selectedLink working). - <Words.Drawer snippet={snippetProps} /> mounted inside the children block alongside the other parts. Visual verification: - Drawer renders as a dedicated right column with the editor content on the left, no layout overlap. - DOCUMENT header + OUTLINE shows "H1 ActiveUIX Words", STATS shows 73 words / 508 chars / 1 min reading, SUGGESTIONS shows "Nothing flagged" (sample doc has no warnings). - Collapse button toggles open/closed via animated inline-size transition. Next: - DRAWER-2: format/image/table/code/list contextual panels wired to runCommand. - DRAWER-3: polish (mobile drawer-as-bottom-sheet, transitions, A/B toggle in demo to compare classic toolbar vs companion drawer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
{#if d.open}
<div data-words-drawer-body>
{#if customChildren}
{@render customChildren(d)}
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
{:else}
{#each modes as mode, i (mode)}
{@const panelOpen = isPanelOpen(mode, modes)}
<section
data-words-drawer-panel
data-mode={mode}
data-open={panelOpen ? '' : undefined}
data-depth={i}
>
feat(words): drawer contextual panels — format/image/table/code/list (DRAWER-2) The companion drawer now switches its body content based on where the caret is. Five context modes wired to runCommand on the snippet provider, all reading current state to show active highlights: - format: B/I/U/S/code mark toggles (data-active reflects activeMarks), align L/C/R/J (data-active reflects currentTextAlign), Add link (URL prompt) + Remove (canUnlink-gated), Clear formatting. - image: read-only view of src, alt (italic + "(missing)" placeholder when empty), caption, status (pending → accent tint, error → threat tint). Edit-image flow deferred to a follow-up (needs a new updateImage operation; for the spike, view-only is enough to validate the panel works). - table: Rows +/- · Columns +/- · Row/Col header toggles · Striped / Compact toggles · Cell tone Plain/Muted/Accent. Every toggle reads current* state so the user sees which buttons are currently applied. - code: Plain + TS/JS/CSS/HTML/JSON/Bash/Py language chips, data-active reflects currentCodeLanguage. - list: Bullet/Numbered/Check kind chips · ⇤ Outdent / ⇥ Indent. All buttons use onmousedown preventDefault so the editor selection isn't dropped when clicking a drawer action (would otherwise lose the target paragraph / table cell / list item). CSS adds a generic .data-words-drawer-action chip (with hover, active, ghost, full, risk tones) + readonly-text style with pending/error tints. Reused across all panels, so adding new panels later means markup-only, no CSS extension. Verified in browser: - Click table cell → drawer shows TABLE panel with Striped active (matches sample's striped=true) and Plain tone active (matches default cell tone). - Click code block → drawer shows CODE panel with TS chip active (matches sample's language='ts'). - Click list item → drawer shows LIST panel with kind + indent actions. Concept A+D validated visually: ONE surface replaces what would otherwise be bubble menu + slash menu + heading picker + language picker + table popover + image controls + link editor. The contextual switch happens automatically; the user just moves the caret. DRAWER-3 (polish): mobile bottom-sheet layout, A/B toggle in demo controls, image-edit flow — deferred to next session if needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
<button
type="button"
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
data-words-drawer-panel-header
aria-expanded={panelOpen}
onclick={() => togglePanel(mode, modes)}
feat(words): drawer contextual panels — format/image/table/code/list (DRAWER-2) The companion drawer now switches its body content based on where the caret is. Five context modes wired to runCommand on the snippet provider, all reading current state to show active highlights: - format: B/I/U/S/code mark toggles (data-active reflects activeMarks), align L/C/R/J (data-active reflects currentTextAlign), Add link (URL prompt) + Remove (canUnlink-gated), Clear formatting. - image: read-only view of src, alt (italic + "(missing)" placeholder when empty), caption, status (pending → accent tint, error → threat tint). Edit-image flow deferred to a follow-up (needs a new updateImage operation; for the spike, view-only is enough to validate the panel works). - table: Rows +/- · Columns +/- · Row/Col header toggles · Striped / Compact toggles · Cell tone Plain/Muted/Accent. Every toggle reads current* state so the user sees which buttons are currently applied. - code: Plain + TS/JS/CSS/HTML/JSON/Bash/Py language chips, data-active reflects currentCodeLanguage. - list: Bullet/Numbered/Check kind chips · ⇤ Outdent / ⇥ Indent. All buttons use onmousedown preventDefault so the editor selection isn't dropped when clicking a drawer action (would otherwise lose the target paragraph / table cell / list item). CSS adds a generic .data-words-drawer-action chip (with hover, active, ghost, full, risk tones) + readonly-text style with pending/error tints. Reused across all panels, so adding new panels later means markup-only, no CSS extension. Verified in browser: - Click table cell → drawer shows TABLE panel with Striped active (matches sample's striped=true) and Plain tone active (matches default cell tone). - Click code block → drawer shows CODE panel with TS chip active (matches sample's language='ts'). - Click list item → drawer shows LIST panel with kind + indent actions. Concept A+D validated visually: ONE surface replaces what would otherwise be bubble menu + slash menu + heading picker + language picker + table popover + image controls + link editor. The contextual switch happens automatically; the user just moves the caret. DRAWER-3 (polish): mobile bottom-sheet layout, A/B toggle in demo controls, image-edit flow — deferred to next session if needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
>
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
<span data-words-drawer-panel-title>{MODE_LABELS[mode]}</span>
{#if panelOpen}
<ChevronUp size="xs" decorative />
{:else}
<ChevronDown size="xs" decorative />
{/if}
feat(words): drawer contextual panels — format/image/table/code/list (DRAWER-2) The companion drawer now switches its body content based on where the caret is. Five context modes wired to runCommand on the snippet provider, all reading current state to show active highlights: - format: B/I/U/S/code mark toggles (data-active reflects activeMarks), align L/C/R/J (data-active reflects currentTextAlign), Add link (URL prompt) + Remove (canUnlink-gated), Clear formatting. - image: read-only view of src, alt (italic + "(missing)" placeholder when empty), caption, status (pending → accent tint, error → threat tint). Edit-image flow deferred to a follow-up (needs a new updateImage operation; for the spike, view-only is enough to validate the panel works). - table: Rows +/- · Columns +/- · Row/Col header toggles · Striped / Compact toggles · Cell tone Plain/Muted/Accent. Every toggle reads current* state so the user sees which buttons are currently applied. - code: Plain + TS/JS/CSS/HTML/JSON/Bash/Py language chips, data-active reflects currentCodeLanguage. - list: Bullet/Numbered/Check kind chips · ⇤ Outdent / ⇥ Indent. All buttons use onmousedown preventDefault so the editor selection isn't dropped when clicking a drawer action (would otherwise lose the target paragraph / table cell / list item). CSS adds a generic .data-words-drawer-action chip (with hover, active, ghost, full, risk tones) + readonly-text style with pending/error tints. Reused across all panels, so adding new panels later means markup-only, no CSS extension. Verified in browser: - Click table cell → drawer shows TABLE panel with Striped active (matches sample's striped=true) and Plain tone active (matches default cell tone). - Click code block → drawer shows CODE panel with TS chip active (matches sample's language='ts'). - Click list item → drawer shows LIST panel with kind + indent actions. Concept A+D validated visually: ONE surface replaces what would otherwise be bubble menu + slash menu + heading picker + language picker + table popover + image controls + link editor. The contextual switch happens automatically; the user just moves the caret. DRAWER-3 (polish): mobile bottom-sheet layout, A/B toggle in demo controls, image-edit flow — deferred to next session if needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
</button>
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
{#if panelOpen}
<div data-words-drawer-panel-body>
{#if mode === 'default'}
{@render defaultPanel(snippet)}
{:else if mode === 'format'}
{@render formatPanel(snippet)}
{:else if mode === 'block'}
{@render blockPanel(snippet)}
{:else if mode === 'cell'}
{@render cellPanel(snippet)}
{:else if mode === 'row'}
{@render rowPanel(snippet)}
{:else if mode === 'table'}
{@render tablePanel(snippet)}
{:else if mode === 'list-item'}
{@render listItemPanel(snippet)}
{:else if mode === 'list'}
{@render listPanel(snippet)}
{:else if mode === 'code'}
{@render codePanel(snippet)}
{:else if mode === 'image'}
{@render imagePanel(snippet)}
{/if}
</div>
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
{/if}
feat(words): drawer contextual panels — format/image/table/code/list (DRAWER-2) The companion drawer now switches its body content based on where the caret is. Five context modes wired to runCommand on the snippet provider, all reading current state to show active highlights: - format: B/I/U/S/code mark toggles (data-active reflects activeMarks), align L/C/R/J (data-active reflects currentTextAlign), Add link (URL prompt) + Remove (canUnlink-gated), Clear formatting. - image: read-only view of src, alt (italic + "(missing)" placeholder when empty), caption, status (pending → accent tint, error → threat tint). Edit-image flow deferred to a follow-up (needs a new updateImage operation; for the spike, view-only is enough to validate the panel works). - table: Rows +/- · Columns +/- · Row/Col header toggles · Striped / Compact toggles · Cell tone Plain/Muted/Accent. Every toggle reads current* state so the user sees which buttons are currently applied. - code: Plain + TS/JS/CSS/HTML/JSON/Bash/Py language chips, data-active reflects currentCodeLanguage. - list: Bullet/Numbered/Check kind chips · ⇤ Outdent / ⇥ Indent. All buttons use onmousedown preventDefault so the editor selection isn't dropped when clicking a drawer action (would otherwise lose the target paragraph / table cell / list item). CSS adds a generic .data-words-drawer-action chip (with hover, active, ghost, full, risk tones) + readonly-text style with pending/error tints. Reused across all panels, so adding new panels later means markup-only, no CSS extension. Verified in browser: - Click table cell → drawer shows TABLE panel with Striped active (matches sample's striped=true) and Plain tone active (matches default cell tone). - Click code block → drawer shows CODE panel with TS chip active (matches sample's language='ts'). - Click list item → drawer shows LIST panel with kind + indent actions. Concept A+D validated visually: ONE surface replaces what would otherwise be bubble menu + slash menu + heading picker + language picker + table popover + image controls + link editor. The contextual switch happens automatically; the user just moves the caret. DRAWER-3 (polish): mobile bottom-sheet layout, A/B toggle in demo controls, image-edit flow — deferred to next session if needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
</section>
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
{/each}
feat(words): drawer contextual panels — format/image/table/code/list (DRAWER-2) The companion drawer now switches its body content based on where the caret is. Five context modes wired to runCommand on the snippet provider, all reading current state to show active highlights: - format: B/I/U/S/code mark toggles (data-active reflects activeMarks), align L/C/R/J (data-active reflects currentTextAlign), Add link (URL prompt) + Remove (canUnlink-gated), Clear formatting. - image: read-only view of src, alt (italic + "(missing)" placeholder when empty), caption, status (pending → accent tint, error → threat tint). Edit-image flow deferred to a follow-up (needs a new updateImage operation; for the spike, view-only is enough to validate the panel works). - table: Rows +/- · Columns +/- · Row/Col header toggles · Striped / Compact toggles · Cell tone Plain/Muted/Accent. Every toggle reads current* state so the user sees which buttons are currently applied. - code: Plain + TS/JS/CSS/HTML/JSON/Bash/Py language chips, data-active reflects currentCodeLanguage. - list: Bullet/Numbered/Check kind chips · ⇤ Outdent / ⇥ Indent. All buttons use onmousedown preventDefault so the editor selection isn't dropped when clicking a drawer action (would otherwise lose the target paragraph / table cell / list item). CSS adds a generic .data-words-drawer-action chip (with hover, active, ghost, full, risk tones) + readonly-text style with pending/error tints. Reused across all panels, so adding new panels later means markup-only, no CSS extension. Verified in browser: - Click table cell → drawer shows TABLE panel with Striped active (matches sample's striped=true) and Plain tone active (matches default cell tone). - Click code block → drawer shows CODE panel with TS chip active (matches sample's language='ts'). - Click list item → drawer shows LIST panel with kind + indent actions. Concept A+D validated visually: ONE surface replaces what would otherwise be bubble menu + slash menu + heading picker + language picker + table popover + image controls + link editor. The contextual switch happens automatically; the user just moves the caret. DRAWER-3 (polish): mobile bottom-sheet layout, A/B toggle in demo controls, image-edit flow — deferred to next session if needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
{/if}
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
</div>
{/if}
{/snippet}
</Words.Drawer>
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3) The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
{#snippet defaultPanel(s: ProviderSnippetProps)}
{@const wordCount = countWordsInText(s.plainText)}
{@const charCount = s.plainText.length}
{@const readingTimeMin = Math.max(1, Math.round(wordCount / 200))}
{@const outline = s.document.children
.map((block, blockIndex) => ({ block, blockIndex }))
.filter(({ block }) => block.type === 'heading')
.map(({ block, blockIndex }) => ({
blockIndex,
level: block.type === 'heading' ? block.level : 1,
text:
block.type === 'heading'
? block.children.map((i) => (i.type === 'text' ? i.text : '')).join('')
: ''
}))
.filter((e) => e.text.trim())}
{@const warnings = collectWarnings(s)}
<div data-words-drawer-section data-flush>
<h4 data-words-drawer-section-title>Outline</h4>
{#if outline.length === 0}
<p data-words-drawer-empty>No headings yet</p>
{:else}
<ol data-words-drawer-outline>
{#each outline as entry (entry.blockIndex)}
<li data-words-drawer-outline-item data-level={entry.level}>
<span data-words-drawer-outline-bullet>H{entry.level}</span>
<span data-words-drawer-outline-text>{entry.text}</span>
</li>
{/each}
</ol>
{/if}
</div>
<div data-words-drawer-section data-flush>
<h4 data-words-drawer-section-title>Stats</h4>
<dl data-words-drawer-stats>
<div><dt>Words</dt><dd>{wordCount}</dd></div>
<div><dt>Chars</dt><dd>{charCount}</dd></div>
<div><dt>Reading</dt><dd>{readingTimeMin} min</dd></div>
</dl>
</div>
<div data-words-drawer-section data-flush>
<h4 data-words-drawer-section-title>
Suggestions
{#if warnings.length > 0}
<span data-words-drawer-section-count>{warnings.length}</span>
{/if}
</h4>
{#if warnings.length === 0}
<p data-words-drawer-empty data-tone="affirm">Nothing flagged</p>
{:else}
<ul data-words-drawer-warnings>
{#each warnings as w (w.id)}
<li data-severity={w.severity}>{w.message}</li>
{/each}
</ul>
{/if}
</div>
{/snippet}
{#snippet formatPanel(s: ProviderSnippetProps)}
<div data-words-drawer-actions>
{#each [
{ id: 'bold', label: 'B', title: 'Bold' },
{ id: 'italic', label: 'I', title: 'Italic' },
{ id: 'underline', label: 'U', title: 'Underline' },
{ id: 'strike', label: 'S', title: 'Strikethrough' },
{ id: 'code', label: '<>', title: 'Inline code' }
] as mark (mark.id)}
<button
type="button"
data-words-drawer-action
data-active={s.activeMarks.includes(mark.id) ? '' : undefined}
title={mark.title}
aria-pressed={s.activeMarks.includes(mark.id)}
onmousedown={(e) => e.preventDefault()}
onclick={() => s.runCommand(mark.id)}
>
{mark.label}
</button>
{/each}
</div>
<div data-words-drawer-actions data-flush>
<button
type="button"
data-words-drawer-action
data-tone="full"
onmousedown={(e) => e.preventDefault()}
onclick={() => {
const url = window.prompt('Link URL:');
if (url) s.insertLink(url);
}}
>
Add link
</button>
{#if s.canUnlink}
<button
type="button"
data-words-drawer-action
onmousedown={(e) => e.preventDefault()}
onclick={() => s.unlink()}
>
Remove
</button>
{/if}
<button
type="button"
data-words-drawer-action
data-tone="ghost"
onmousedown={(e) => e.preventDefault()}
onclick={() => s.runCommand('clear-formatting')}
>
Clear
</button>
</div>
{/snippet}
{#snippet blockPanel(s: ProviderSnippetProps)}
<div data-words-drawer-section-subtitle>Text align</div>
<div data-words-drawer-actions>
{#each [
{ id: 'align-left', label: '⫷', title: 'Left' },
{ id: 'align-center', label: '☰', title: 'Center' },
{ id: 'align-right', label: '⫸', title: 'Right' },
{ id: 'align-justify', label: '☷', title: 'Justify' }
] as a (a.id)}
<button
type="button"
data-words-drawer-action
data-active={s.currentTextAlign === a.id.replace('align-', '') ? '' : undefined}
title={a.title}
onmousedown={(e) => e.preventDefault()}
onclick={() => s.runCommand(a.id)}
>
{a.label}
</button>
{/each}
</div>
{/snippet}
{#snippet cellPanel(s: ProviderSnippetProps)}
<div data-words-drawer-section-subtitle>Tone</div>
<div data-words-drawer-actions>
{#each [
{ id: 'table-cell-tone-default', label: 'Plain' },
{ id: 'table-cell-tone-muted', label: 'Muted' },
{ id: 'table-cell-tone-accent', label: 'Accent' }
] as t (t.id)}
<button
type="button"
data-words-drawer-action
data-active={s.currentTableCellTone === t.id.replace('table-cell-tone-', '') ? '' : undefined}
onclick={() => s.runCommand(t.id)}
>{t.label}</button>
{/each}
</div>
<div data-words-drawer-section-subtitle>Vertical</div>
<div data-words-drawer-actions>
{#each [
{ id: 'table-cell-vertical-top', label: '⬆ Top' },
{ id: 'table-cell-vertical-middle', label: '◆ Mid' },
{ id: 'table-cell-vertical-bottom', label: '⬇ Bot' }
] as v (v.id)}
<button
type="button"
data-words-drawer-action
data-active={s.currentTableCellVerticalAlign === v.id.replace('table-cell-vertical-', '') ? '' : undefined}
onclick={() => s.runCommand(v.id)}
>{v.label}</button>
{/each}
</div>
<div data-words-drawer-section-subtitle>Text align</div>
<div data-words-drawer-actions>
{#each [
{ id: 'align-left', label: '⫷' },
{ id: 'align-center', label: '☰' },
{ id: 'align-right', label: '⫸' },
{ id: 'align-justify', label: '☷' }
] as a (a.id)}
<button
type="button"
data-words-drawer-action
data-active={s.currentTextAlign === a.id.replace('align-', '') ? '' : undefined}
onclick={() => s.runCommand(a.id)}
>{a.label}</button>
{/each}
</div>
{/snippet}
{#snippet rowPanel(s: ProviderSnippetProps)}
<div data-words-drawer-actions>
<button type="button" data-words-drawer-action onclick={() => s.runCommand('insert-table-row')}>+ Row</button>
<button type="button" data-words-drawer-action data-tone="risk" onclick={() => s.runCommand('delete-table-row')}>− Row</button>
<button
type="button"
data-words-drawer-action
data-active={s.currentTableHeaderRowActive ? '' : undefined}
onclick={() => s.runCommand('toggle-table-header-row')}
>Header</button>
</div>
{/snippet}
{#snippet tablePanel(s: ProviderSnippetProps)}
<div data-words-drawer-section-subtitle>Columns</div>
<div data-words-drawer-actions>
<button type="button" data-words-drawer-action onclick={() => s.runCommand('insert-table-column')}>+ Col</button>
<button type="button" data-words-drawer-action data-tone="risk" onclick={() => s.runCommand('delete-table-column')}>− Col</button>
<button
type="button"
data-words-drawer-action
data-active={s.currentTableHeaderColumnActive ? '' : undefined}
onclick={() => s.runCommand('toggle-table-header-column')}
>Header</button>
</div>
<div data-words-drawer-section-subtitle>Appearance</div>
<div data-words-drawer-actions>
<button
type="button"
data-words-drawer-action
data-active={s.currentTableStriped ? '' : undefined}
onclick={() => s.runCommand('toggle-table-striped')}
>Striped</button>
<button
type="button"
data-words-drawer-action
data-active={s.currentTableCompact ? '' : undefined}
onclick={() => s.runCommand('toggle-table-compact')}
>Compact</button>
</div>
{/snippet}
{#snippet listItemPanel(s: ProviderSnippetProps)}
<div data-words-drawer-actions>
<button type="button" data-words-drawer-action onclick={() => s.runCommand('decrease-indent')}>⇤ Outdent</button>
<button type="button" data-words-drawer-action onclick={() => s.runCommand('increase-indent')}>⇥ Indent</button>
</div>
{/snippet}
{#snippet listPanel(s: ProviderSnippetProps)}
<div data-words-drawer-section-subtitle>Kind</div>
<div data-words-drawer-actions>
<button type="button" data-words-drawer-action onclick={() => s.runCommand('unordered-list')}>Bullet</button>
<button type="button" data-words-drawer-action onclick={() => s.runCommand('ordered-list')}>Numbered</button>
<button type="button" data-words-drawer-action onclick={() => s.runCommand('check-list')}>Check</button>
</div>
{/snippet}
{#snippet codePanel(s: ProviderSnippetProps)}
<div data-words-drawer-actions>
{#each CODE_LANGUAGES as lang (lang.id)}
<button
type="button"
data-words-drawer-action
data-active={s.currentCodeLanguage === lang.id ? '' : undefined}
onclick={() => s.setCodeLanguage(lang.id || undefined)}
>{lang.label}</button>
{/each}
</div>
{/snippet}
{#snippet imagePanel(s: ProviderSnippetProps)}
{@const block = imageBlockAtCaret(s)}
{#if block}
<div data-words-drawer-section-subtitle>Source</div>
<p data-words-drawer-readonly-text>{block.src}</p>
<div data-words-drawer-section-subtitle>Alt</div>
<p data-words-drawer-readonly-text data-empty={!block.alt}>
{block.alt || '(missing)'}
</p>
{#if block.caption}
<div data-words-drawer-section-subtitle>Caption</div>
<p data-words-drawer-readonly-text>{block.caption}</p>
{/if}
{#if block.status}
<div data-words-drawer-section-subtitle>Status</div>
<p data-words-drawer-readonly-text data-tone={block.status}>
{block.status === 'pending' ? 'Uploading…' : 'Upload failed'}
</p>
{/if}
{:else}
<p data-words-drawer-empty>Place caret on an image to edit it.</p>
{/if}
{/snippet}
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON) Closes the architectural gap from the DRAWER spike: the companion drawer now follows the same morfo/soma/eidos pattern as every other Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.). DRAWER-CANON-1 — morfo + soma: - wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group', defaultElement 'aside', role 'complementary'). Declares data-words- drawer-mode (default/format/image/table/code/list), data-open, data-disabled, aria-label. New event 'commit-toggle-drawer' fires on collapse/expand for sema/telemetry hooks. - New WordsDrawerProvider in words-provider.svelte: owns `open` bindable state, derives `mode` from parent provider's currentBlock + selection (same logic as the spike, now in soma where it belongs), exposes snippetProps {mode, open, disabled, snippet, toggle, setOpen}. `toggle()` and `setOpen()` emit the event. - WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types added to types.ts and re-exported via exports.ts. - soma/components/words/components/words-drawer.svelte (the soma component) mounts the provider and forwards merged props to either the `child` snippet or the default <aside> render. DRAWER-CANON-2 — eidos refactor: - eidos words-drawer.svelte rewritten as a consumer of <Words.Drawer>. The shell (aside + header + collapse button) and the per-mode body now live inside the children snippet that receives the soma's drawer snippet props. Mode derivation removed from eidos (it's now in soma). Local open $state removed (now bindable via the soma component). - Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />` collapses to plain `<Words.Drawer />` — the soma provider hands the parent snippet down internally. DRAWER-CANON-3 — verify: - 214/214 tests pass in soma+morfo scope (no regressions). - npx tsc --noEmit clean for words area. - Visual verification: drawer still renders with DOCUMENT/OUTLINE/ STATS/SUGGESTIONS in default mode; context-switching to table / code / list / format still works; aside element now carries role='complementary', aria-label from morfo translationRef, data-words-drawer-mode reflecting current panel. The eidos drawer now passes the same canon checks as every other Words part: morfo declares its contract, soma owns the state, eidos only paints. The schema validator can verify the drawer's attrs, the eidos lint can check selector usage, sema can react to the toggle event. Demo header chips reflect the new contract: PARTS 19→20, EVENTS 11→12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
<script context="module" lang="ts">
import type { ProviderSnippetProps as PSP } from '$soma/components/words';
type Warning = {
readonly id: string;
readonly severity: 'info' | 'warn';
readonly message: string;
};
export function collectWarnings(s: PSP): readonly Warning[] {
const out: Warning[] = [];
s.document.children.forEach((block, index) => {
if (block.type === 'image' && !block.alt?.trim()) {
out.push({
id: `alt-${index}`,
severity: 'warn',
message: `Image at block ${index + 1} has no alt text`
});
}
if (block.type === 'paragraph') {
const text = block.children
.map((i) => (i.type === 'text' ? i.text : ''))
.join('');
if (text.length > 600) {
out.push({
id: `long-${index}`,
severity: 'info',
message: `Paragraph ${index + 1} is long (${text.length} chars) — consider splitting`
});
}
}
});
const headingLevels = s.document.children
.filter((b) => b.type === 'heading')
.map((b) => (b.type === 'heading' ? b.level : 0));
for (let i = 1; i < headingLevels.length; i++) {
if (headingLevels[i] - headingLevels[i - 1] > 1) {
out.push({
id: `skip-${i}`,
severity: 'warn',
message: `Heading h${headingLevels[i]} appears without an h${headingLevels[i - 1] + 1} above — structure skip`
});
break;
}
}
return out;
}
</script>

Powered by TurnKey Linux.