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 {
|
|
|
|
|
|
// Atomic block selection wins (image is contenteditable=false so
|
|
|
|
|
|
// the text selection rarely points at it). Falls back to the
|
|
|
|
|
|
// caret's anchor block when the user hasn't atomic-clicked.
|
|
|
|
|
|
const blockIndex =
|
|
|
|
|
|
s.selectedBlockIndex !== undefined
|
|
|
|
|
|
? s.selectedBlockIndex
|
|
|
|
|
|
: s.selection?.anchor.path[0];
|
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 (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;
|
|
|
|
|
|
|
|
|
|
|
|
/** Hand-picked palette — keeps the picker compact while covering
|
|
|
|
|
|
* most editorial intents. Free hex still available via the input
|
|
|
|
|
|
* next to the chips. */
|
|
|
|
|
|
const COLOR_PRESETS = [
|
|
|
|
|
|
'#ef4444', // red
|
|
|
|
|
|
'#f97316', // orange
|
|
|
|
|
|
'#eab308', // amber
|
|
|
|
|
|
'#22c55e', // green
|
|
|
|
|
|
'#06b6d4', // cyan
|
|
|
|
|
|
'#3b82f6', // blue
|
|
|
|
|
|
'#8b5cf6', // violet
|
|
|
|
|
|
'#ec4899', // pink
|
|
|
|
|
|
'#ffffff', // white
|
|
|
|
|
|
'#a1a1aa' // grey
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
/** Read the parametric mark value for a given prefix from the
|
|
|
|
|
|
* active marks. Returns '' when not set. */
|
|
|
|
|
|
function currentParametricMark(
|
|
|
|
|
|
marks: readonly string[],
|
|
|
|
|
|
prefix: 'color' | 'bgcolor'
|
|
|
|
|
|
): string {
|
|
|
|
|
|
const found = marks.find((m) => m.startsWith(`${prefix}:`));
|
|
|
|
|
|
return found ? found.slice(prefix.length + 1) : '';
|
|
|
|
|
|
}
|
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-section-subtitle>Foreground</div>
|
|
|
|
|
|
<div data-words-drawer-color-picker>
|
|
|
|
|
|
{#each COLOR_PRESETS as preset (preset)}
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
data-words-drawer-color-swatch
|
|
|
|
|
|
data-active={currentParametricMark(s.activeMarks, 'color') === preset ? '' : undefined}
|
|
|
|
|
|
style="background-color:{preset}"
|
|
|
|
|
|
title={preset}
|
|
|
|
|
|
aria-label={`Set foreground to ${preset}`}
|
|
|
|
|
|
onmousedown={(e) => e.preventDefault()}
|
|
|
|
|
|
onclick={() => s.applyCommand({ type: 'toggleMark', mark: `color:${preset}` as never })}
|
|
|
|
|
|
></button>
|
|
|
|
|
|
{/each}
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="color"
|
|
|
|
|
|
data-words-drawer-color-input
|
|
|
|
|
|
value={currentParametricMark(s.activeMarks, 'color') || '#000000'}
|
|
|
|
|
|
aria-label="Pick custom foreground color"
|
|
|
|
|
|
onchange={(e) => {
|
|
|
|
|
|
const v = (e.currentTarget as HTMLInputElement).value;
|
|
|
|
|
|
s.applyCommand({ type: 'toggleMark', mark: `color:${v}` as never });
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{#if currentParametricMark(s.activeMarks, 'color')}
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
data-words-drawer-color-clear
|
|
|
|
|
|
title="Clear foreground"
|
|
|
|
|
|
aria-label="Clear foreground color"
|
|
|
|
|
|
onmousedown={(e) => e.preventDefault()}
|
|
|
|
|
|
onclick={() => s.applyCommand({ type: 'toggleMark', mark: 'color:' as never })}
|
|
|
|
|
|
>×</button>
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div data-words-drawer-section-subtitle>Background</div>
|
|
|
|
|
|
<div data-words-drawer-color-picker>
|
|
|
|
|
|
{#each COLOR_PRESETS as preset (preset)}
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
data-words-drawer-color-swatch
|
|
|
|
|
|
data-active={currentParametricMark(s.activeMarks, 'bgcolor') === preset ? '' : undefined}
|
|
|
|
|
|
style="background-color:{preset}"
|
|
|
|
|
|
title={preset}
|
|
|
|
|
|
aria-label={`Set background to ${preset}`}
|
|
|
|
|
|
onmousedown={(e) => e.preventDefault()}
|
|
|
|
|
|
onclick={() => s.applyCommand({ type: 'toggleMark', mark: `bgcolor:${preset}` as never })}
|
|
|
|
|
|
></button>
|
|
|
|
|
|
{/each}
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="color"
|
|
|
|
|
|
data-words-drawer-color-input
|
|
|
|
|
|
value={currentParametricMark(s.activeMarks, 'bgcolor') || '#ffff00'}
|
|
|
|
|
|
aria-label="Pick custom background color"
|
|
|
|
|
|
onchange={(e) => {
|
|
|
|
|
|
const v = (e.currentTarget as HTMLInputElement).value;
|
|
|
|
|
|
s.applyCommand({ type: 'toggleMark', mark: `bgcolor:${v}` as never });
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{#if currentParametricMark(s.activeMarks, 'bgcolor')}
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
data-words-drawer-color-clear
|
|
|
|
|
|
title="Clear background"
|
|
|
|
|
|
aria-label="Clear background color"
|
|
|
|
|
|
onmousedown={(e) => e.preventDefault()}
|
|
|
|
|
|
onclick={() => s.applyCommand({ type: 'toggleMark', mark: 'bgcolor:' as never })}
|
|
|
|
|
|
>×</button>
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
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
|
|
|
|
<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>
|