@quillmark/svelte 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +2 -2
  2. package/THEMING.md +11 -7
  3. package/dist/core/address.d.ts +7 -7
  4. package/dist/core/address.js +6 -6
  5. package/dist/core/bloom.d.ts +5 -5
  6. package/dist/core/bloom.js +8 -8
  7. package/dist/core/codec/anchor.d.ts +1 -1
  8. package/dist/core/codec/anchor.js +1 -1
  9. package/dist/core/codec/atoms.d.ts +10 -0
  10. package/dist/core/codec/atoms.js +78 -0
  11. package/dist/core/codec/clipboard.d.ts +59 -0
  12. package/dist/core/codec/clipboard.js +202 -0
  13. package/dist/core/codec/code.d.ts +1 -1
  14. package/dist/core/codec/code.js +4 -4
  15. package/dist/core/codec/decode.d.ts +1 -1
  16. package/dist/core/codec/decode.js +8 -8
  17. package/dist/core/codec/encode.d.ts +4 -4
  18. package/dist/core/codec/encode.js +10 -10
  19. package/dist/core/codec/field.d.ts +21 -26
  20. package/dist/core/codec/field.js +33 -32
  21. package/dist/core/codec/index.js +5 -8
  22. package/dist/core/codec/inputrules.js +56 -18
  23. package/dist/core/codec/islands.d.ts +11 -7
  24. package/dist/core/codec/islands.js +16 -10
  25. package/dist/core/codec/keymap.d.ts +5 -6
  26. package/dist/core/codec/keymap.js +8 -7
  27. package/dist/core/codec/lists.d.ts +3 -3
  28. package/dist/core/codec/lists.js +32 -13
  29. package/dist/core/codec/marks.d.ts +1 -1
  30. package/dist/core/codec/marks.js +1 -1
  31. package/dist/core/codec/positions.js +1 -1
  32. package/dist/core/codec/prose.css +230 -215
  33. package/dist/core/codec/schema.js +4 -4
  34. package/dist/core/codec/slash.d.ts +39 -41
  35. package/dist/core/codec/slash.js +0 -0
  36. package/dist/core/codec/table-view.d.ts +12 -16
  37. package/dist/core/codec/table-view.js +488 -292
  38. package/dist/core/codec/table.d.ts +42 -15
  39. package/dist/core/codec/table.js +70 -26
  40. package/dist/core/errors.d.ts +5 -5
  41. package/dist/core/index.js +2 -2
  42. package/dist/core/lifecycle.js +1 -1
  43. package/dist/core/rebind.svelte.d.ts +2 -2
  44. package/dist/core/rebind.svelte.js +4 -4
  45. package/dist/core/teardown.d.ts +1 -1
  46. package/dist/core/teardown.js +1 -1
  47. package/dist/core/theme.css +144 -54
  48. package/dist/preset/index.css +1 -1
  49. package/dist/preset/recipes.css +31 -29
  50. package/dist/preset/scale.css +36 -25
  51. package/dist/preview/Preview.svelte +12 -10
  52. package/dist/preview/Preview.svelte.d.ts +3 -3
  53. package/dist/preview/bridge.js +4 -4
  54. package/dist/preview/controller.js +3 -3
  55. package/dist/preview/geometry.d.ts +2 -2
  56. package/dist/preview/geometry.js +2 -2
  57. package/dist/preview/index.js +3 -3
  58. package/dist/preview/overlay.d.ts +1 -1
  59. package/dist/preview/overlay.js +3 -3
  60. package/dist/preview/paint.js +15 -17
  61. package/dist/visual/ArrayField.svelte +80 -66
  62. package/dist/visual/ArrayField.svelte.d.ts +1 -1
  63. package/dist/visual/BooleanField.svelte +8 -6
  64. package/dist/visual/BooleanField.svelte.d.ts +2 -2
  65. package/dist/visual/Card.svelte +151 -325
  66. package/dist/visual/Card.svelte.d.ts +2 -6
  67. package/dist/visual/CardControls.svelte +1 -1
  68. package/dist/visual/DateField.svelte +19 -19
  69. package/dist/visual/DateField.svelte.d.ts +3 -3
  70. package/dist/visual/DiagnosticList.svelte +1 -1
  71. package/dist/visual/EnumField.svelte +16 -16
  72. package/dist/visual/EnumField.svelte.d.ts +2 -2
  73. package/dist/visual/Field.svelte +128 -115
  74. package/dist/visual/Field.svelte.d.ts +1 -1
  75. package/dist/visual/FieldHint.svelte +24 -42
  76. package/dist/visual/FieldHint.svelte.d.ts +1 -1
  77. package/dist/visual/FieldLabel.svelte +8 -8
  78. package/dist/visual/FormatPopover.svelte +80 -35
  79. package/dist/visual/NumberField.svelte +5 -5
  80. package/dist/visual/NumberField.svelte.d.ts +1 -1
  81. package/dist/visual/ObjectField.svelte +9 -9
  82. package/dist/visual/ObjectField.svelte.d.ts +1 -1
  83. package/dist/visual/ProseArrayElement.svelte +11 -13
  84. package/dist/visual/ProseArrayElement.svelte.d.ts +1 -1
  85. package/dist/visual/ProseField.svelte +53 -84
  86. package/dist/visual/ProseField.svelte.d.ts +12 -17
  87. package/dist/visual/SlashMenu.svelte +42 -16
  88. package/dist/visual/TextField.svelte +5 -5
  89. package/dist/visual/TextField.svelte.d.ts +1 -1
  90. package/dist/visual/TipsCard.svelte +43 -28
  91. package/dist/visual/VisualEditor.svelte +6 -6
  92. package/dist/visual/VisualEditorInner.svelte +122 -129
  93. package/dist/visual/controls.css +74 -30
  94. package/dist/visual/diagnostics.d.ts +5 -5
  95. package/dist/visual/diagnostics.js +3 -3
  96. package/dist/visual/domid.js +3 -3
  97. package/dist/visual/ext.d.ts +1 -1
  98. package/dist/visual/ext.js +1 -1
  99. package/dist/visual/icons/nodes.d.ts +3 -0
  100. package/dist/visual/icons/nodes.js +2 -1
  101. package/dist/visual/index.js +4 -4
  102. package/dist/visual/leaves.d.ts +3 -3
  103. package/dist/visual/leaves.js +1 -1
  104. package/dist/visual/links.d.ts +24 -0
  105. package/dist/visual/links.js +67 -0
  106. package/dist/visual/props.d.ts +8 -8
  107. package/dist/visual/signals.d.ts +6 -6
  108. package/dist/visual/strings.d.ts +14 -19
  109. package/dist/visual/strings.js +5 -7
  110. package/dist/visual/structure.d.ts +24 -17
  111. package/dist/visual/structure.js +8 -8
  112. package/dist/visual/synced.svelte.d.ts +1 -1
  113. package/dist/visual/synced.svelte.js +4 -4
  114. package/dist/visual/tips.d.ts +2 -2
  115. package/dist/visual/tips.js +3 -3
  116. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  <!--
2
- The federated WYSIWYG surface (VISUAL_EDITOR). A THIN composition over many
3
- small editors; NOT one PM document spanning the page.
2
+ The federated WYSIWYG surface (VISUAL_EDITOR). A thin composition over many
3
+ small editors; not one PM document spanning the page.
4
4
 
5
- MOUNTED ONCE PER DOCUMENT. `VisualEditor` keys this component on `doc`, so
5
+ Mounted once per document. `VisualEditor` keys this component on `doc`, so
6
6
  everything below seeds from a handle that cannot change under it: `cardIds`, the
7
7
  id `seq`, the leaf registry, the commit-error map, the active address and the
8
8
  card refs are all mount-scoped, and the prose leaves close over the `doc` they
@@ -12,21 +12,21 @@
12
12
  It owns:
13
13
  • structure: the schema × payload join, re-derived from the live `Document`;
14
14
  • stable card identity: a session-id array reordered in lockstep with the
15
- content, resolved to an index ONLY at the mutation boundary;
15
+ content, resolved to an index only at the mutation boundary;
16
16
  • commit routing: prose leaves lower to `applyChange` (in the codec); scalars/
17
17
  arrays/objects go through the typed `writer`; structure through the mutators;
18
18
  • focus + the bridge outputs (`onActiveLeafChange`, `onCaretMove`) and the
19
19
  `setCaret(at)` entry wired to the preview;
20
- • the ONE formatting popover (`FormatPopover`, mounted once, observing the
20
+ • the one formatting popover (`FormatPopover`, mounted once, observing the
21
21
  active leaf via `getActiveLeaf`) and diagnostics routing (`diagnostics.ts`:
22
22
  quill.validate + local commit errors + the external `diagnostics` prop,
23
23
  merged into `diagByKey` and threaded to each `<Field>`/card body).
24
24
 
25
- REACTIVITY ACROSS THE WASM HANDLE. The `Document` is opaque to Svelte, so a
25
+ Reactivity across the WASM handle. The `Document` is opaque to Svelte, so a
26
26
  `revision` counter is bumped after every scalar/structure mutation and the card
27
- tree is `$derived` by RE-READING `doc.main`/`doc.cards`/`quill.schema`. Prose
28
- leaves are mounted ONCE per stable leaf key (keyed `<Card>`/`<ProseField>`);
29
- they commit to the doc directly and do NOT bump `revision`, so a re-derive never
27
+ tree is `$derived` by re-reading `doc.main`/`doc.cards`/`quill.schema`. Prose
28
+ leaves are mounted once per stable leaf key (keyed `<Card>`/`<ProseField>`);
29
+ they commit to the doc directly and do not bump `revision`, so a re-derive never
30
30
  remounts them or drops a caret. `doc.main`/`doc.cards` allocate per read: read
31
31
  once per derive.
32
32
  -->
@@ -66,7 +66,6 @@
66
66
  stringifyGhost,
67
67
  resolveBodyGhost,
68
68
  NO_RESOLVED_ROWS,
69
- type BodyPlaceholder,
70
69
  type CardModel,
71
70
  type ResolvedCardRows
72
71
  } from './structure.js';
@@ -122,7 +121,7 @@
122
121
  // ── Reactivity + session identity ───────────────────────────────────────────
123
122
  let revision = $state(0);
124
123
  // This editor's own id, prefixed onto every field's DOM ids (`domid.ts`). The
125
- // leaf-key space is unique per EDITOR, not per page: two editors mounted
124
+ // leaf-key space is unique per editor, not per page: two editors mounted
126
125
  // side-by-side both hold `main:subject`, and a duplicate `id` makes `for` resolve
127
126
  // to whichever mounted first. `$props.id()` is stable across SSR and hydration,
128
127
  // which a module counter is not.
@@ -150,13 +149,13 @@
150
149
  /**
151
150
  * Re-derive the card tree and report the edit. Called for the two lanes that go
152
151
  * through this component; a prose leaf commits itself and reports through
153
- * `proseChanged`, which does NOT bump the revision (bumping it would re-derive
152
+ * `proseChanged`, which does not bump the revision (bumping it would re-derive
154
153
  * and remount every leaf, costing the caret on every keystroke).
155
154
  *
156
155
  * `cardId` is passed rather than derived from `at`: the sites below hold the id
157
156
  * already, and the removal has no address to derive one from. `at` is an `Addr` for
158
- * a leaf and a CARD INDEX for a structure op, whose subject is the card rather than
159
- * anything inside it; either way the path is minted HERE, after the bump, because
157
+ * a leaf and a card index for a structure op, whose subject is the card rather than
158
+ * anything inside it; either way the path is minted here, after the bump, because
160
159
  * an op's own index is only addressable against the tree the bump re-derived (an
161
160
  * insert's card does not exist in the previous one, and a retype's kind is the
162
161
  * previous kind).
@@ -181,7 +180,7 @@
181
180
  }
182
181
 
183
182
  // ── Teardown (core/teardown.ts: unregister, cancel, then free) ──────────────
184
- // A document swap is a REMOUNT (see the remount contract above), so a destroy is
183
+ // A document swap is a remount (see the remount contract above), so a destroy is
185
184
  // the only way this surface ends and one span covers both. Cancellers register
186
185
  // below in the order they run: the registry that resolves new work first, then
187
186
  // the work already scheduled. The document handle is the consumer's to free, so
@@ -207,7 +206,7 @@
207
206
  let mainCard = $state<CardHandle | undefined>(undefined);
208
207
  let cardRefs = $state<(CardHandle | undefined)[]>([]);
209
208
  /** The stack's own element: it carries `data-qm-root`, so it is what the kind
210
- * menu portals INTO, the way each leaf's surface resolves its nearest root. */
209
+ * menu portals into, the way each leaf's surface resolves its nearest root. */
211
210
  let rootEl = $state<HTMLElement | undefined>(undefined);
212
211
 
213
212
  // ── Focus + bridge outputs ──────────────────────────────────────────────────
@@ -234,7 +233,7 @@
234
233
  * common case allocates nothing. */
235
234
  const NO_KINDS: readonly string[] = [];
236
235
  /**
237
- * The live kinds by content index, off the DERIVED card tree rather than
236
+ * The live kinds by content index, off the derived card tree rather than
238
237
  * `doc.cards`: they are already in hand, and `doc.cards` serializes every card on
239
238
  * each read, which is not a thing to do per keystroke.
240
239
  */
@@ -248,13 +247,13 @@
248
247
  function pathFor(addr: Addr): DocPath | undefined {
249
248
  return fieldPathForAddr(addr, addr.card == null ? NO_KINDS : liveKinds());
250
249
  }
251
- // The last PLACE reported, so a caret that did not move is not news. A leaf fires
250
+ // The last place reported, so a caret that did not move is not news. A leaf fires
252
251
  // one caret signal per transaction, and a transaction need not have moved the
253
252
  // caret to exist: a mark toggle over the same range, an anchor op, a plugin's own
254
253
  // bookkeeping. The memo is the editor's rather than each leaf's, because a place
255
254
  // spans leaves: a caret that leaves `main.body` at 5 for a card leaf and comes
256
255
  // back to 5 made two moves, and a per-leaf memo would swallow the second. An edit
257
- // that reflows the page UNDER a caret holding its offset is the case this drops,
256
+ // that reflows the page under a caret holding its offset is the case this drops,
258
257
  // and a preview does not need the signal to answer it: the place is unchanged, so
259
258
  // the one it already followed is the one its recompile re-locates.
260
259
  let lastCaretField: DocPath | undefined;
@@ -276,18 +275,18 @@
276
275
 
277
276
  // ── Commit routing ──────────────────────────────────────────────────────────
278
277
  // Scalars / arrays / objects → the typed writer (schema-checked). Prose leaves
279
- // commit themselves via the codec (applyChange) and do NOT pass through here.
278
+ // commit themselves via the codec (applyChange) and do not pass through here.
280
279
  // A scalar control commits `undefined` for a cleared entry: the unset lane
281
280
  // below (`doc.removeField`), not a write.
282
281
  //
283
- // A bad value makes `writer.set` THROW a `QuillmarkError`, whose
282
+ // A bad value makes `writer.set` throw a `QuillmarkError`, whose
284
283
  // `diagnostics[0]` carries a `code` and a canonical `path` (e.g.
285
284
  // `edit::field_coercion_failed` at `main.font_size`, or `edit::unknown_field`). The
286
- // editor already KNOWS the field/card being committed, so it KEYS the entry
287
- // from THAT address: id-keyed so it survives a later card reorder, never
285
+ // editor already knows the field/card being committed, so it keys the entry
286
+ // from that address: id-keyed so it survives a later card reorder, never
288
287
  // parsed from the positional path; while surfacing the thrown diagnostic
289
288
  // verbatim (its `code`/`message`) as the payload (VISUAL_EDITOR §Diagnostics,
290
- // producer #2). It is stashed in `commitErrors`; a subsequent SUCCESSFUL commit
289
+ // producer #2). It is stashed in `commitErrors`; a subsequent successful commit
291
290
  // for the same field clears it. Nothing here gates: the value is not written
292
291
  // (the document is unchanged on throw, per the boundary's own transactional
293
292
  // contract) and editing continues.
@@ -296,14 +295,14 @@
296
295
  const keyStr = fieldKeyToString(key);
297
296
  try {
298
297
  if (value === undefined) {
299
- // The UNSET rung of the commitment ladder (a cleared scalar control,
300
- // VISUAL_EDITOR §"Structure mirrors the schema"): REMOVE the field so the
298
+ // The unset rung of the commitment ladder (a cleared scalar control,
299
+ // VISUAL_EDITOR §"Structure mirrors the schema"): remove the field so the
301
300
  // engine's authored › `default:` › zero-fill resolve renders the
302
301
  // default, rather than baking a snapshot the schema can't track
303
302
  // (canon SCHEMAS.md: the engine never persists a default; nor do we).
304
303
  // Removal writes no value, so there is nothing for a schema to conform:
305
304
  // it goes through the quill-free `doc.removeField` (bare string = main
306
- // `{ field }`; `{ card, field }` for a card), NOT the typed writer.
305
+ // `{ field }`; `{ card, field }` for a card), not the typed writer.
307
306
  if (isMain) {
308
307
  doc.removeField(name);
309
308
  } else {
@@ -436,7 +435,7 @@
436
435
  });
437
436
  // No addr: the removed card has no address left, and the surviving cards'
438
437
  // addresses all shifted. The stack changed, not a leaf. The id still names
439
- // WHICH card went, which is the one thing a host keying on it needs, and the
438
+ // which card went, which is the one thing a host keying on it needs, and the
440
439
  // only handle the removal leaves it.
441
440
  bump('structure', id);
442
441
  }
@@ -462,7 +461,7 @@
462
461
  bump('structure', id, i);
463
462
  }
464
463
  /**
465
- * Clear the tips channel: the dismissal write, and the ONLY write
464
+ * Clear the tips channel: the dismissal write, and the only write
466
465
  * tips make. `undefined` drops the key while `title` and any later sibling ride
467
466
  * through; `ext.ts` holds why that matters.
468
467
  */
@@ -475,7 +474,7 @@
475
474
  }
476
475
 
477
476
  // ── Addressing + per-card op bundle ─────────────────────────────────────────
478
- /** A LIVE card address: `card` is a getter, so a reorder re-targets in place. */
477
+ /** A live card address: `card` is a getter, so a reorder re-targets in place. */
479
478
  function cardAddr(id: string, field?: string): Addr {
480
479
  return field != null
481
480
  ? ({
@@ -496,9 +495,7 @@
496
495
  }
497
496
 
498
497
  // ── Diagnostics routing (VISUAL_EDITOR §Diagnostics) ────────────────────────
499
- // Producer #1: quill.validate(doc), re-run every revision (empirically always
500
- // `[]` for usaf_memo (no field in the fixture carries a `!must_fill` marker)
501
- // but the routing exists for the general contract, not just this fixture).
498
+ // Producer #1: quill.validate(doc), re-run every revision.
502
499
  const validation = $derived.by(() => {
503
500
  revision; // re-run on every mutation, per VISUAL_EDITOR §Diagnostics
504
501
  try {
@@ -525,7 +522,7 @@
525
522
  return mergeDiagnostics(fromValidate, fromExternal, [...commitErrors.values()]);
526
523
  });
527
524
  function opsFor(id: string, isMain: boolean) {
528
- // ONE identity per field: the leaf registry, the DOM's three names, and the
525
+ // One identity per field: the leaf registry, the DOM's three names, and the
529
526
  // diagnostics map all key off this string, so a leaf, its label and its
530
527
  // diagnostics can only ever resolve together.
531
528
  const leafKey = (field?: string) => fieldKeyToString({ card: isMain ? undefined : id, field });
@@ -547,33 +544,6 @@
547
544
  };
548
545
  }
549
546
 
550
- // ── The empty-body ghost's consumer wording ─────────────────────────────────
551
- // The hook is consulted once per KIND and its answer kept for the session, which
552
- // is the whole determinism guarantee: a hook that samples a witty set at random
553
- // is impure by design, and this cache is what makes its answer look chosen:
554
- // same string for every card of a kind, and the same one after a remount or any
555
- // re-derive. Keyed by kind rather than by card id deliberately: two empty cards
556
- // that are the same kind ARE the same invitation, and disagreeing ghosts read as
557
- // a glitch. Retyping a card crosses to another key and re-asks, which is right:
558
- // it is a different card now. Plain (non-`$state`) on purpose: memoization, so
559
- // filling it during a derive must not feed back into one.
560
- let ghostHook: BodyPlaceholder | undefined;
561
- const ghostByKind = new Map<string, string | undefined>();
562
- function customBodyGhost(kind: string, isMain: boolean): string | undefined {
563
- const bodyPlaceholder = merged.bodyPlaceholder;
564
- // A swapped hook invalidates every answer the old one gave.
565
- if (bodyPlaceholder !== ghostHook) {
566
- ghostByKind.clear();
567
- ghostHook = bodyPlaceholder;
568
- }
569
- if (!bodyPlaceholder) return undefined;
570
- // `main` is not a `card_kinds` key, so a kind that spells it collides without
571
- // the flag in the key.
572
- const key = `${isMain ? '1' : '0'}\0${kind}`;
573
- if (!ghostByKind.has(key)) ghostByKind.set(key, bodyPlaceholder({ kind, isMain }));
574
- return ghostByKind.get(key);
575
- }
576
-
577
547
  // ── The derived card tree (schema × payload join) ───────────────────────────
578
548
  function buildCard(
579
549
  id: string,
@@ -612,12 +582,14 @@
612
582
  // same text-ghost projection `<Field>` applies to a field's row; and falls
613
583
  // back to an invitation where a scalar shows nothing, because an empty body
614
584
  // is a surface to write on and an empty control is a value not yet given.
615
- // Asked only for a card that HAS a body, so the hook is never consulted
616
- // about one that renders none.
585
+ // Asked only for a card that has a body, so the hook is never consulted
586
+ // about one that renders none. The hook is pure by contract, so it is called
587
+ // straight from the derive and its answer kept nowhere: a ghost holds still
588
+ // across a re-derive because the function does.
617
589
  bodyGhost: hasBody
618
590
  ? resolveBodyGhost(
619
591
  stringifyGhost(ghostDefault(rows.body ?? undefined)),
620
- customBodyGhost(kind, isMain),
592
+ merged.bodyPlaceholder?.({ cardId: id, kind, isMain }),
621
593
  merged.bodyGhost
622
594
  )
623
595
  : undefined
@@ -671,13 +643,13 @@
671
643
  /**
672
644
  * Resolve a preview {@link Landing} to a mounted leaf and land in it.
673
645
  *
674
- * Async because the reveal has to RENDER before the landing: a collapsed group
646
+ * Async because the reveal has to render before the landing: a collapsed group
675
647
  * is `inert`, which swallows a focus silently, so a caret placed in the same
676
648
  * tick as the reveal would go nowhere and report nothing. The consumer's
677
649
  * `onPick` ignores the promise: awaiting it is for a caller that wants to
678
650
  * observe where the caret went.
679
651
  *
680
- * A destroy lands INSIDE this one: `leaf` is looked up before the flush and
652
+ * A destroy lands inside this one: `leaf` is looked up before the flush and
681
653
  * dispatched into after it, and a PM view destroyed in that window throws on the
682
654
  * dispatch. A consumer's `onPick` outlives the surface it points at too, so the
683
655
  * span is asked on the way in as well as after the await.
@@ -686,15 +658,15 @@
686
658
  const found = await revealLeaf(at.field);
687
659
  if (!found) return missed(`no mounted field at ${at.field}`, at.field);
688
660
  // A `'segment'` hit landed on origin-less ink (list markers, a code fence's
689
- // interior): `pos` is the segment START, not a cluster-exact caret
661
+ // interior): `pos` is the segment start, not a cluster-exact caret
690
662
  // (HitGranularity), so it is dropped rather than snapped to a spot the click
691
663
  // did not resolve. `'cluster'` (and an absent granularity: the backend did not
692
- // report it, treat as exact) places the caret. An absent `pos` is the PLACEMENT
664
+ // report it, treat as exact) places the caret. An absent `pos` is the placement
693
665
  // rung and reaches the same floor.
694
666
  land(found, at.granularity === 'segment' ? undefined : at.pos);
695
667
  // The arrival cue. Unconditional, unlike the preview side's change-guarded
696
668
  // bloom: a preview click is one discrete act, and its commonest target is the
697
- // leaf ALREADY focused (where landing a caret changes nothing on screen) or
669
+ // leaf already focused (where landing a caret changes nothing on screen) or
698
670
  // one off-screen, where the browser's focus-scroll moves the page and leaves
699
671
  // the caret to be hunted for in a long form.
700
672
  bloomInside(found.control.el);
@@ -709,7 +681,7 @@
709
681
 
710
682
  /**
711
683
  * Reveal the field at `field` and hand back its landing handle plus the target it
712
- * resolved to, once the reveal has RENDERED. The shared half of the two landing
684
+ * resolved to, once the reveal has rendered. The shared half of the two landing
713
685
  * verbs: a collapsed group is clipped to zero height and sits inside an `inert`
714
686
  * panel, which swallows a focus silently, so a caret placed in the same tick as the
715
687
  * reveal goes nowhere and reports nothing. Exactly one card holds the key; the rest
@@ -717,10 +689,10 @@
717
689
  *
718
690
  * The target comes back with the handle because the caller needs its key to ask for
719
691
  * the codec lane (`leaves.prose`), and minting it twice would be two parses of one
720
- * path. The reveal itself is the KEY's: a group holds the field, and an element
692
+ * path. The reveal itself is the key's: a group holds the field, and an element
721
693
  * address opens the same group its array sits in.
722
694
  *
723
- * A destroy lands INSIDE this: the field is looked up before the flush and
695
+ * A destroy lands inside this: the field is looked up before the flush and
724
696
  * dispatched into after it, and a PM view destroyed in that window throws on the
725
697
  * dispatch. A consumer's call outlives the surface it points at too, so the span is
726
698
  * asked on the way in as well as after the await.
@@ -739,10 +711,10 @@
739
711
  /**
740
712
  * Put the caret in a revealed target, at the finest grain it can take.
741
713
  *
742
- * - An ELEMENT address focuses that row, with no caret inside it: an array element
714
+ * - An element address focuses that row, with no caret inside it: an array element
743
715
  * is no `createField` leaf and its handle is a bare focus.
744
716
  * - A prose leaf with a `pos` takes the caret at that USV offset.
745
- * - Everything else takes the focus alone: the offset is a position in RENDERED
717
+ * - Everything else takes the focus alone: the offset is a position in rendered
746
718
  * content and a form control has no coordinate to spend it in (an
747
719
  * `<input type="number">` refuses a selection outright), which is also the whole
748
720
  * of what a click on plate-placed ink can mean.
@@ -765,9 +737,9 @@
765
737
  // They speak the public vocabulary — a `DocPath` for a place, a `CardId` for a
766
738
  // card — so a host drives them with what the hooks handed it.
767
739
  //
768
- // A target the surface does not hold is a NO-OP that reports `target-unknown` at
740
+ // A target the surface does not hold is a no-op that reports `target-unknown` at
769
741
  // `dev`: a key from a previous session or a card already removed, or a path naming
770
- // no declared field — an ELEMENT path (`main.references.0`) whose array is
742
+ // no declared field — an element path (`main.references.0`) whose array is
771
743
  // undeclared or unmounted included, that being the only way one misses. `setCaret`
772
744
  // reports it too, being the verb a preview click drives: a landing that resolved
773
745
  // nothing is indistinguishable from one that landed, and a consumer wiring the
@@ -815,7 +787,7 @@
815
787
  }
816
788
 
817
789
  /** What a `DocPath` resolves to in the mounted tree: a leaf key, and the array
818
- * ELEMENT within it when the address names one. */
790
+ * element within it when the address names one. */
819
791
  interface LeafTarget {
820
792
  key: string;
821
793
  element?: number;
@@ -828,13 +800,13 @@
828
800
  * `addrForFieldPath` route the diagnostics take, the absolute card index resolved
829
801
  * to its live stable id, then the shared `fieldKeyToString` form.
830
802
  *
831
- * TWO RUNGS, because the boundary mints addresses at a finer granularity than
803
+ * Two rungs, because the boundary mints addresses at a finer granularity than
832
804
  * `Addr` can name: a `richtext[]` element surfaces as `main.references.0`, which
833
805
  * the grammar reads as a field literally named `"0"`. The second rung reads that
834
- * trailing segment as an INDEX, under a field the schema declares an array — which
806
+ * trailing segment as an index, under a field the schema declares an array — which
835
807
  * is why the ladder is the editor's (VISUAL_EDITOR.md §Surface): the preview
836
808
  * carries no schema, and truncating the address there is worse than guessing, since
837
- * `pos` is an offset into the ELEMENT's own content.
809
+ * `pos` is an offset into the element's own content.
838
810
  */
839
811
  function leafTargetFor(field: DocPath): LeafTarget | undefined {
840
812
  const direct = addrForFieldPath(field);
@@ -863,7 +835,7 @@
863
835
  </script>
864
836
 
865
837
  <div class="qm-editor {className ?? ''}" {style} data-qm-root bind:this={rootEl}>
866
- <!-- `main` and the tips card are ONE block in the stack: the tips card tucks under
838
+ <!-- `main` and the tips card are one block in the stack: the tips card tucks under
867
839
  `main`'s bottom corners, so the two share a seam rather than a gutter and the
868
840
  wrapper is what holds them to it. -->
869
841
  <div class="qm-primary">
@@ -895,7 +867,7 @@
895
867
  </div>
896
868
 
897
869
  {@render addAffordance(0)}
898
- <!-- A card and the gap under it are one SLOT, which is what a reorder moves: the
870
+ <!-- A card and the gap under it are one slot, which is what a reorder moves: the
899
871
  strip below a card is the same strip wherever the card lands, so it rides along
900
872
  rather than being slid across. It is also the shape `animate:` asks for, being the
901
873
  keyed block's only child. -->
@@ -925,47 +897,46 @@
925
897
 
926
898
  <FormatPopover {getActiveLeaf} />
927
899
 
928
- <!-- Cards always render; the ADD affordance is gated on the schema declaring
900
+ <!-- Cards always render; the add affordance is gated on the schema declaring
929
901
  `card_kinds`, since there is nothing to seed otherwise. A card already in the
930
902
  document shows regardless of its kind: a kind with no schema (foreign, or a
931
903
  schema with no `card_kinds` at all) degrades to a recovery shell inside <Card>
932
904
  (retype + delete), never gated away, so its content is neither dropped nor
933
- trapped. The gate lives HERE rather than at each call site: the strip is one
905
+ trapped. The gate lives here rather than at each call site: the strip is one
934
906
  decision, and two copies of it drift into a stack with a gap at one end. -->
935
907
  {#snippet addAffordance(atIndex: number)}
936
908
  {#if kinds.length}
937
- <!-- The strip past the last card is the APPEND point, the one a reader looks for
909
+ <!-- The strip past the last card is the append point, the one a reader looks for
938
910
  rather than finds by position, so it alone states itself in words at rest. The
939
- gaps between cards stay bare: the strip IS the gap, and the pill each fills on
911
+ gaps between cards stay bare: the strip is the gap, and the pill each fills on
940
912
  hover draws what a label would state, the space the new card takes. -->
941
913
  {@const marked = atIndex === model.cards.length}
942
914
  <div class="qm-add-card" class:qm-add-card-marked={marked}>
943
- <!-- Marked, the words ARE the accessible name and no `aria-label` doubles
944
- them; bare, the kind is the whole reading, the one with no geometry to
945
- carry it. -->
915
+ <!-- Marked, the words are the accessible name and no `aria-label` doubles
916
+ them; bare, the same words land as the label, the strip having no
917
+ geometry to carry them. -->
946
918
  {#if kinds.length === 1}
947
919
  <button
948
920
  type="button"
949
- class="qm-add-btn qm-add-affordance"
950
- aria-label={marked ? undefined : merged.addCardOfKind(humanize(kinds[0]))}
951
- onclick={() => addCard(atIndex, kinds[0])}
952
- >{marked ? merged.addCardMark(humanize(kinds[0])) : ''}</button
921
+ class="qm-add-btn qm-add-affordance qm-tap-floor"
922
+ aria-label={marked ? undefined : merged.addCard}
923
+ onclick={() => addCard(atIndex, kinds[0])}>{marked ? merged.addCard : ''}</button
953
924
  >
954
925
  {:else}
955
- <!-- Multi-kind add: pick the kind, then seed + insert. A MENU rather than a
926
+ <!-- Multi-kind add: pick the kind, then seed + insert. A menu rather than a
956
927
  disclosure: it floats out of the stack, so raising it moves no card, and
957
928
  it dismisses on pick, on Escape and on a click outside, none of which a
958
929
  `<details>` does. The trigger is bits-ui's `<button>`, which is why the
959
930
  rules below reach it through `:global`. -->
960
931
  <DropdownMenu.Root>
961
932
  <DropdownMenu.Trigger
962
- class="qm-add-btn qm-add-affordance"
933
+ class="qm-add-btn qm-add-affordance qm-tap-floor"
963
934
  aria-label={marked ? undefined : merged.addCard}
964
- >{marked ? merged.addCardMarkAny : ''}</DropdownMenu.Trigger
935
+ >{marked ? merged.addCard : ''}</DropdownMenu.Trigger
965
936
  >
966
937
  <DropdownMenu.Portal to={rootEl}>
967
938
  <DropdownMenu.Content sideOffset={4}>
968
- <!-- Portalled out of the row but INTO the stack's root, and carrying the
939
+ <!-- Portalled out of the row but into the stack's root, and carrying the
969
940
  marker itself: floating is still a detached subtree to the
970
941
  derivation, like FormatPopover and the enum listbox. -->
971
942
  <div class="qm-menu-surface" data-qm-root>
@@ -985,33 +956,40 @@
985
956
 
986
957
  <style>
987
958
  /* The private scale lands via `data-qm-root` on the root element above: this is
988
- a DETACHED root, one of those core/theme.css applies the derivation to. The
959
+ a detached root, one of those core/theme.css applies the derivation to. The
989
960
  root rule also carries the baseline font and colour, so nothing here restates
990
961
  them. Nothing here mints; `check:style` enforces that. */
991
- /* The COLUMN, not only the cards in it: the gutter the stack sits in and the tone
962
+ /* The column, not only the cards in it: the gutter the stack sits in and the tone
992
963
  behind it are the surface's own, so a bare `<div>` is a mounting site and
993
964
  nothing is owed before the editor looks right. Consumer CSS is unlayered and
994
965
  beats all of it, which is how a host that wants the column back takes it.
995
- `border-box`, so a height from the caller is the height this draws. */
966
+ `border-box`, so a height from the caller is the height this draws.
967
+
968
+ The sunken rung, which is what makes a card an island: the cards are the base
969
+ plane and this is the one they float on, so the gutter reads as ground rather
970
+ than as more card (ARCHITECTURE §Styling). The inset is the widest rung on the
971
+ ramp, which is what gives the ground enough of itself to read as ground: at a
972
+ narrower one the stack meets whatever frames the surface and the islands are
973
+ back to being one block clipped at its edges. */
996
974
  .qm-editor {
997
975
  box-sizing: border-box;
998
976
  display: flex;
999
977
  flex-direction: column;
1000
978
  gap: var(--_qm-space-2);
1001
- padding: var(--_qm-space-2);
1002
- background: var(--_qm-surface);
979
+ padding: var(--_qm-space-4);
980
+ background: var(--_qm-surface-sunken);
1003
981
  color: var(--_qm-ink);
1004
982
  }
1005
- /* A fixed-height pane, which the stack is NOT by default, and the four popovers
983
+ /* A fixed-height pane, which the stack is not by default, and the four popovers
1006
984
  are the reason: each resolves its portal target with
1007
985
  `closest('[data-qm-root]')`, which is this element, so an `overflow` here
1008
986
  clips the menu a leaf raised. Mounted in a page that scrolls, nothing clips;
1009
987
  mounted in a pane that does, the host says so and takes the clipping it would
1010
- have had from its own scrolling frame either way. The TAIL rides with the
988
+ have had from its own scrolling frame either way. The tail rides with the
1011
989
  scroller because it is only meaningful under one: dead space below the last
1012
990
  card, so it can be read mid-pane rather than against the bottom edge.
1013
991
 
1014
- The HEIGHT is what makes the rest of the rule mean anything, and it is the
992
+ The height is what makes the rest of the rule mean anything, and it is the
1015
993
  pane's rather than a number of ours: `overflow` on a box that grows to its
1016
994
  content never has anything to scroll, so the stack would run past the pane
1017
995
  and be clipped by whatever the host frames it with. Taking the pane's height
@@ -1029,7 +1007,7 @@
1029
1007
  padding-block-end: var(--_qm-tail);
1030
1008
  }
1031
1009
  /* The stack's one gapless seam, which `TipsCard` draws and this holds the two
1032
- blocks to. `main` has to paint OVER the tip: a later sibling paints over an earlier
1010
+ blocks to. `main` has to paint over the tip: a later sibling paints over an earlier
1033
1011
  one otherwise, and the tip's background would cover the corners it is there to
1034
1012
  fill. `isolation` keeps that z-index local, so one wrapper owns the ordering and no
1035
1013
  card below inherits an argument about it. The wrapper is a single flex item, so the
@@ -1051,56 +1029,71 @@
1051
1029
  gap: var(--_qm-space-2);
1052
1030
  }
1053
1031
  /* The strip between two blocks, and the whole of it is the target: a gap is found
1054
- by POSITION, so reveal and hit region are the full-bleed row rather than a word
1055
- to aim at in the middle of it. It takes the editor's gap back on BOTH sides, so
1056
- it is not a control sitting in the gutter; it IS the gutter: what separates two
1057
- cards is the trigger's own height at the tap floor and nothing else, and the
1058
- pill it fills on hover is edge to edge the space the new card opens into. No
1059
- band is held back as miss-tolerance: gutter that reads as the trigger and
1060
- inserts nothing unsays what the fill claims, so a press anywhere between two
1061
- cards inserts. Absorbed rather than removed, because `gap` is also what separates
1062
- the one seam no strip sits in: every card from the next under a quill declaring no
1063
- kinds, where the affordance does not render at all. */
1032
+ by position, so reveal and hit region are the full-bleed row rather than a word
1033
+ to aim at in the middle of it. It takes the editor's gap back on both sides, so
1034
+ it is not a control sitting in the gutter; it is the gutter: what separates two
1035
+ cards is the trigger's own height and nothing else, and the pill it fills on hover
1036
+ is edge to edge the space the new card opens into. No band is held back as
1037
+ miss-tolerance: gutter that reads as the trigger and inserts nothing unsays what
1038
+ the fill claims, so a press anywhere between two cards inserts. Absorbed rather
1039
+ than removed, because `gap` is also what separates the one seam no strip sits in:
1040
+ every card from the next under a quill declaring no kinds, where the affordance
1041
+ does not render at all. */
1064
1042
  .qm-add-card {
1065
1043
  display: flex;
1066
1044
  margin-top: calc(var(--_qm-space-2) * -1);
1067
1045
  margin-bottom: calc(var(--_qm-space-2) * -1);
1068
1046
  }
1069
1047
  /* Unboxed, like every button, and not dashed: a
1070
- dashed edge is the PLACEHOLDER idiom ("nothing is here yet") which on a button
1048
+ dashed edge is the placeholder idiom ("nothing is here yet") which on a button
1071
1049
  reads as disabled or as a drop target. It stays honest in one place, the
1072
1050
  un-schemable card (`Card.svelte`), which is a state rather than a control. A bare
1073
- trigger rests invisible, so the FILL is the whole of what it shows: the pill fills
1051
+ trigger rests invisible, so the fill is the whole of what it shows: the pill fills
1074
1052
  the strip because the strip is what was pressed, and the strip is the gap, so the
1075
1053
  fill is the card-to-be drawn where it will land. Every gap is an equal entry point,
1076
1054
  reached by the name rather than by a mark: a card goes anywhere in the stack, not
1077
1055
  just after the last one.
1078
1056
 
1057
+ No inset of its own: the pill and the gap are the same rectangle, and a bare strip is
1058
+ a band of gutter, so its height is a rhythm rung. The press floor the box gives up is
1059
+ `.qm-tap-floor`'s (controls.css); what it overhangs is the card's own inset above and
1060
+ below, which holds no target, so the floor costs a neighbour nothing.
1061
+
1062
+ Ink is pinned to the label tone in every state, taking back the family's hover step:
1063
+ the trigger arrives and leaves on opacity, and a tone moving under an opacity already
1064
+ moving reads as two trips at different speeds.
1065
+
1079
1066
  `:global`, because the multi-kind trigger is bits-ui's own element and a `class`
1080
1067
  passed to a primitive is a plain string that never picks up the scoping hash:
1081
1068
  the same seam the enum trigger is styled through. */
1082
1069
  .qm-add-card :global(.qm-add-btn) {
1083
- /* No inset of its own: the pill and the gap are the same rectangle, and the tap
1084
- floor is the whole of the height, which is what the marked strip's words
1085
- centre in. */
1086
1070
  display: flex;
1087
1071
  align-items: center;
1088
1072
  justify-content: center;
1089
1073
  width: 100%;
1074
+ height: var(--_qm-space-4);
1075
+ color: var(--_qm-ink-label);
1076
+ opacity: 0;
1077
+ transition:
1078
+ opacity var(--_qm-reveal),
1079
+ background-color var(--_qm-reveal);
1090
1080
  }
1091
- /* Words at rest, so the marked strip restates the rung they take: the LABEL rung,
1092
- chrome's size, off the body size the family's type rule hands every button
1093
- (`controls.css`), which is the size the fields above are READ at. */
1081
+ /* The strip that holds words at rest takes the height that centres them, and rests dim
1082
+ rather than absent, having something to be seen. */
1094
1083
  .qm-add-card-marked :global(.qm-add-btn) {
1084
+ height: var(--_qm-tap-min);
1095
1085
  font-size: var(--_qm-text-label);
1086
+ font-weight: var(--_qm-weight-mid);
1087
+ opacity: var(--_qm-opacity-idle);
1096
1088
  }
1097
- /* The two engaged states the shared `:hover` rung does not cover, both filling the
1098
- same pill it does. Keyboard focus, because the fill is the only thing that reads
1099
- as the trigger on a bare strip; and an open menu, because it portals out of the
1100
- strip, so a pointer moving onto an item has left the row whose hover was drawing
1089
+ /* One arrival, three ways in. Keyboard focus, because the fill is the only thing that
1090
+ reads as the trigger on a bare strip; and an open menu, because it portals out of
1091
+ the strip, so a pointer moving onto an item has left the row whose hover was drawing
1101
1092
  the pill the menu hangs from. */
1093
+ .qm-add-card :global(.qm-add-btn:hover),
1102
1094
  .qm-add-card :global(.qm-add-btn:focus-visible),
1103
1095
  .qm-add-card :global(.qm-add-btn[data-state='open']) {
1104
- background: var(--_qm-surface-hover);
1096
+ opacity: 1;
1097
+ background: var(--_qm-add-fill);
1105
1098
  }
1106
1099
  </style>