smoodly 0.0.12 → 0.0.13
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.
- package/README.md +219 -25
- package/dist/admin/editor/CanvasOverlay.d.ts +6 -2
- package/dist/admin/editor/CanvasOverlay.js +9 -11
- package/dist/admin/editor/EditorView.js +130 -25
- package/dist/admin/editor/FloatingToolbar.d.ts +16 -0
- package/dist/admin/editor/FloatingToolbar.js +14 -0
- package/dist/admin/editor/InlineRichtext.d.ts +9 -0
- package/dist/admin/editor/InlineRichtext.js +50 -0
- package/dist/admin/editor/Outline.d.ts +16 -10
- package/dist/admin/editor/Outline.js +13 -10
- package/dist/admin/editor/inline-dom.d.ts +27 -0
- package/dist/admin/editor/inline-dom.js +43 -0
- package/dist/admin/editor/inline-session.d.ts +30 -0
- package/dist/admin/editor/inline-session.js +19 -0
- package/dist/admin/editor/protocol.d.ts +22 -1
- package/dist/admin/editor/useCanvasBridge.d.ts +8 -4
- package/dist/admin/editor/useCanvasBridge.js +12 -7
- package/dist/admin/editor/useInlineSession.d.ts +46 -0
- package/dist/admin/editor/useInlineSession.js +244 -0
- package/dist/admin/field-paths.d.ts +13 -0
- package/dist/admin/field-paths.js +49 -0
- package/dist/admin/forms/FieldWidget.d.ts +7 -2
- package/dist/admin/forms/FieldWidget.js +12 -40
- package/dist/admin/forms/ListField.d.ts +10 -0
- package/dist/admin/forms/ListField.js +42 -0
- package/dist/admin/forms/MediaWidget.js +10 -3
- package/dist/admin/forms/RichtextElementCard.d.ts +3 -0
- package/dist/admin/forms/RichtextElementCard.js +37 -0
- package/dist/admin/forms/RichtextImageCard.d.ts +3 -0
- package/dist/admin/forms/RichtextImageCard.js +42 -0
- package/dist/admin/forms/RichtextInsertMenu.d.ts +6 -0
- package/dist/admin/forms/RichtextInsertMenu.js +6 -0
- package/dist/admin/forms/RichtextPopovers.d.ts +7 -0
- package/dist/admin/forms/RichtextPopovers.js +25 -0
- package/dist/admin/forms/RichtextToolbar.d.ts +4 -2
- package/dist/admin/forms/RichtextToolbar.js +4 -2
- package/dist/admin/forms/RichtextWidget.d.ts +4 -1
- package/dist/admin/forms/RichtextWidget.js +23 -120
- package/dist/admin/forms/elements-context.d.ts +7 -0
- package/dist/admin/forms/elements-context.js +13 -0
- package/dist/admin/forms/reveal.d.ts +5 -0
- package/dist/admin/forms/reveal.js +47 -0
- package/dist/admin/forms/richtext-extensions.d.ts +8 -0
- package/dist/admin/forms/richtext-extensions.js +11 -0
- package/dist/admin/forms/richtext-form-context.d.ts +16 -0
- package/dist/admin/forms/richtext-form-context.js +18 -0
- package/dist/admin/forms/richtext-insert.d.ts +38 -0
- package/dist/admin/forms/richtext-insert.js +37 -0
- package/dist/admin/forms/richtext-nodes.d.ts +37 -0
- package/dist/admin/forms/richtext-nodes.js +85 -0
- package/dist/admin/forms/richtext-paste.js +5 -0
- package/dist/admin/forms/tabs.d.ts +3 -0
- package/dist/admin/forms/tabs.js +6 -0
- package/dist/admin/forms/useRichtextEditor.d.ts +51 -0
- package/dist/admin/forms/useRichtextEditor.js +281 -0
- package/dist/admin/next/bridge.js +51 -20
- package/dist/admin/ops-impl.js +13 -8
- package/dist/admin/richtext-doc.d.ts +3 -0
- package/dist/admin/richtext-doc.js +12 -0
- package/dist/admin/serialize.d.ts +7 -0
- package/dist/admin/serialize.js +16 -1
- package/dist/admin/shell/AdminApp.js +2 -1
- package/dist/admin/shell/EntriesList.js +34 -12
- package/dist/admin/shell/EntryForm.js +22 -15
- package/dist/admin/shell/IconRail.js +6 -5
- package/dist/admin/shell/ListView.d.ts +9 -2
- package/dist/admin/shell/ListView.js +7 -6
- package/dist/admin/shell/LoginView.js +2 -2
- package/dist/admin/shell/PagesList.js +53 -24
- package/dist/admin/shell/SharedForm.js +17 -11
- package/dist/admin/shell/SharedList.js +5 -3
- package/dist/admin/shell/entries-list.d.ts +4 -0
- package/dist/admin/shell/entries-list.js +6 -0
- package/dist/admin/shell/pages-tree.d.ts +24 -0
- package/dist/admin/shell/pages-tree.js +59 -1
- package/dist/admin/tree-ops.d.ts +9 -1
- package/dist/admin/tree-ops.js +27 -0
- package/dist/admin/ui/LocaleSwitcher.d.ts +10 -0
- package/dist/admin/ui/LocaleSwitcher.js +28 -8
- package/dist/admin/ui/Modal.js +14 -2
- package/dist/admin/ui/ResizeHandle.d.ts +1 -0
- package/dist/admin/ui/ResizeHandle.js +127 -0
- package/dist/admin/ui/primitives.d.ts +29 -4
- package/dist/admin/ui/primitives.js +29 -10
- package/dist/admin/ui/resize.d.ts +16 -0
- package/dist/admin/ui/resize.js +23 -0
- package/dist/admin/ui/sortable.d.ts +23 -0
- package/dist/admin/ui/sortable.js +57 -0
- package/dist/admin/ui/theme.d.ts +3 -1
- package/dist/admin/ui/theme.js +512 -165
- package/dist/admin/ui/useSortable.d.ts +35 -0
- package/dist/admin/ui/useSortable.js +220 -0
- package/dist/admin/validate.d.ts +6 -1
- package/dist/admin/validate.js +28 -9
- package/dist/config.d.ts +1 -0
- package/dist/config.js +36 -0
- package/dist/entry-store.d.ts +5 -2
- package/dist/entry-store.js +4 -3
- package/dist/fields.d.ts +4 -0
- package/dist/fields.js +2 -2
- package/dist/image/SmoodlyImage.d.ts +4 -1
- package/dist/image/SmoodlyImage.js +2 -2
- package/dist/index.d.ts +8 -1
- package/dist/index.js +5 -1
- package/dist/marks.d.ts +53 -0
- package/dist/marks.js +43 -0
- package/dist/media.d.ts +3 -2
- package/dist/media.js +48 -6
- package/dist/refs.d.ts +2 -2
- package/dist/refs.js +33 -12
- package/dist/render.js +2 -1
- package/dist/resolve.d.ts +8 -2
- package/dist/resolve.js +8 -7
- package/dist/richtext/index.d.ts +12 -0
- package/dist/richtext/index.js +9 -0
- package/dist/richtext-render.d.ts +22 -2
- package/dist/richtext-render.js +40 -13
- package/dist/richtext-walk.d.ts +44 -0
- package/dist/richtext-walk.js +46 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +13 -1
- package/dist/section-wrapper.js +2 -1
- package/dist/site.d.ts +2 -0
- package/dist/site.js +7 -4
- package/dist/store.d.ts +3 -1
- package/dist/store.js +4 -4
- package/dist/supabase-entry-store.d.ts +1 -0
- package/dist/supabase-entry-store.js +2 -1
- package/dist/supabase-store.js +1 -1
- package/dist/toolset.d.ts +7 -1
- package/dist/toolset.js +13 -4
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -205,8 +205,9 @@ Implemented:
|
|
|
205
205
|
`AdminLayout` for the app's `[[...segments]]` route group;
|
|
206
206
|
`createAdminOpHandler` is the one server action the client ops call),
|
|
207
207
|
and `smoodly/bridge` (`EditorBridge`, the only piece that ships to the
|
|
208
|
-
SITE side — runs inside the draft-route iframe, reports node
|
|
209
|
-
geometry, forwards select/hover
|
|
208
|
+
SITE side — runs inside the draft-route iframe, reports node, zone and
|
|
209
|
+
marked-field geometry, forwards select/hover with the field under the
|
|
210
|
+
pointer, executes refresh/scroll, reports each committed render). Shell
|
|
210
211
|
(registry-derived nav, list views, its own client-side router —
|
|
211
212
|
`shell/router.ts`, `RouteLink`, a leave guard for dirty forms; the
|
|
212
213
|
shell mounts from the layout and the catch-all page only makes the
|
|
@@ -217,8 +218,9 @@ Implemented:
|
|
|
217
218
|
canvas — the iframe is the site's own draft route, pixel-identical by
|
|
218
219
|
construction; every gesture mutates client tree state through pure
|
|
219
220
|
ops, a debounced `saveDraft` persists, and the bridge refreshes the
|
|
220
|
-
iframe on the save ack — refresh-on-change preview
|
|
221
|
-
|
|
221
|
+
iframe on the save ack — refresh-on-change preview for structure; text
|
|
222
|
+
and richtext are edited in place in the canvas and inspector text edits
|
|
223
|
+
land there the same tick, spec 2026-09-10) are all real, in-house. The autosave itself is a framework-free
|
|
222
224
|
controller (`createDraftAutosave`: debounce, doubling capped retry,
|
|
223
225
|
pre-publish `flush`) wrapped by `useDraftAutosave`; the controller is
|
|
224
226
|
unit-tested with fake timers, the hook only for its initial snapshot. Proven end-to-end in
|
|
@@ -247,8 +249,10 @@ Implemented:
|
|
|
247
249
|
derived path as the Path column, and per row its kind, status, sibling
|
|
248
250
|
position and whether it may take children), rendered by
|
|
249
251
|
`admin/shell/PagesList.tsx` with a row menu over the tree ops (New
|
|
250
|
-
subpage, New
|
|
251
|
-
Delete
|
|
252
|
+
subpage, New subpath, Add page here, Rename…, Move up / Move down,
|
|
253
|
+
Delete); a mount a fixed page claims opens that index page's editor
|
|
254
|
+
on click like any page row (2026-09-09), a bare mount opens the
|
|
255
|
+
collection list. The editor
|
|
252
256
|
is keyed by id, with a locale switcher loading that locale's own tree
|
|
253
257
|
and pointing the canvas at `urls[locale]` and, while no block is
|
|
254
258
|
selected, page settings (`admin/editor/PageSettings.tsx`: the title and
|
|
@@ -320,6 +324,10 @@ Implemented:
|
|
|
320
324
|
because Next loads `next.config.ts` in plain Node, where anything that
|
|
321
325
|
reaches `next/image` fails to resolve (next@16 has no `exports` map);
|
|
322
326
|
`smoodly/image` still re-exports the helper for code the bundler sees.
|
|
327
|
+
- `smoodly/richtext` — the bound richtext renderer, `createRichText({
|
|
328
|
+
elements, sizes })`: the root `RichText` with the project's elements and
|
|
329
|
+
body images through `<SmoodlyImage>`. Its own entry for the same reason
|
|
330
|
+
as `smoodly/image`: it reaches `next/image`.
|
|
323
331
|
|
|
324
332
|
- Locked zones are usable: `fillLockedZones` (a pure tree op) materializes
|
|
325
333
|
the one node of every empty locked zone from the component's `sample`
|
|
@@ -362,11 +370,20 @@ Implemented:
|
|
|
362
370
|
a toolset forbids survives every round trip. Links carry `href` only. An
|
|
363
371
|
empty document is missing for a required field. The widget emits plain
|
|
364
372
|
JSON: ProseMirror's node attrs have a null prototype, which a server
|
|
365
|
-
action cannot serialize.
|
|
373
|
+
action cannot serialize. Block nodes (spec 2026-09-08 block nodes): an
|
|
374
|
+
`image` node (media library, caption, alt override) and one generic
|
|
375
|
+
`element` node for every registered Element, always in the schema,
|
|
376
|
+
inserted through the toolbar's "+" or a `/` menu, edited as cards (the
|
|
377
|
+
element card renders its fields through `FieldWidget`, nested richtext
|
|
378
|
+
included). `RichText` takes `elements` and an `image` render prop;
|
|
379
|
+
`smoodly/richtext` exports `createRichText` bound over `<SmoodlyImage>`.
|
|
380
|
+
The media and refs walks descend into documents against the element
|
|
381
|
+
schemas (`elements` beside `sections` in the store, resolve and site
|
|
382
|
+
options).
|
|
366
383
|
|
|
367
|
-
Not yet:
|
|
368
|
-
|
|
369
|
-
|
|
384
|
+
Not yet: canvas drag-and-drop (list fields, the outline, manual
|
|
385
|
+
collections and the pages tree drag since 2026-09-09 — spec
|
|
386
|
+
`docs/superpowers/specs/2026-09-09-drag-and-drop-design.md`), undo/redo, sample/placeholder content
|
|
370
387
|
marking, per-locale section visibility conditions (OQ #3 — each locale
|
|
371
388
|
now has its own tree, so a section's fields are already per locale; what
|
|
372
389
|
is open is hiding a section by a locale-dependent condition).
|
|
@@ -390,6 +407,114 @@ after both are on the registry. From the repo root:
|
|
|
390
407
|
`prepublishOnly` rebuilds each package. The Claude Code skill
|
|
391
408
|
`.claude/skills/releasing-smoodly` carries the same order with its checks.
|
|
392
409
|
|
|
410
|
+
## Follow-ups (logged 2026-09-10, inline editing spec)
|
|
411
|
+
|
|
412
|
+
- Shared placements ignore field clicks: their fields live in the shared
|
|
413
|
+
item under Globals, which the page editor cannot patch. The starter's
|
|
414
|
+
Header and Footer carry marks already; inline editing of shared content
|
|
415
|
+
needs the shared form's ops behind the canvas.
|
|
416
|
+
- Richtext instant write from the inspector: the pure `RichText` render
|
|
417
|
+
swapped into the container while the widget is typed in.
|
|
418
|
+
- Item-level chrome in the canvas ("Key points · 3 of 4" on hover, add,
|
|
419
|
+
remove, reorder in place, later drag within a list): the item marks are
|
|
420
|
+
the hook.
|
|
421
|
+
- Placeholder and validation marks on the page (the sample-sourced
|
|
422
|
+
placeholder treatment, "required, empty" outlines) wait for the tree's
|
|
423
|
+
placeholder marking.
|
|
424
|
+
- A coverage hint for developers: the admin knows a section's text fields
|
|
425
|
+
and could list the ones without a mark.
|
|
426
|
+
- AI verbs ("rewrite this heading") need a field under the cursor, which
|
|
427
|
+
a mark is.
|
|
428
|
+
- A cross-origin admin needs the bridge-side editing variant (spec §3).
|
|
429
|
+
- Direct-child spacing rules on a richtext container (Tailwind `space-y-*`,
|
|
430
|
+
`.prose > p`) do not reach the canvas editor's blocks during a session;
|
|
431
|
+
flex and grid gaps are copied (onto the ProseMirror root since the
|
|
432
|
+
walk, 69aecc6). Documented, not fixed.
|
|
433
|
+
- The text session's Enter commits: a plain text field has no line breaks.
|
|
434
|
+
A multi-line text control would be its own descriptor.
|
|
435
|
+
- The floating toolbar flips below the caret only near the top of the
|
|
436
|
+
DOCUMENT (where the page box clips it); it scrolls with the content, so
|
|
437
|
+
a caret at the top of the scrolled canvas has its toolbar out of view
|
|
438
|
+
until the editor scrolls. A flip keyed to the scroller's viewport would
|
|
439
|
+
fix it.
|
|
440
|
+
- No starter section gives the canvas a richtext with blocks: the Image
|
|
441
|
+
and text body is marks-only, and the article body (the `article`
|
|
442
|
+
toolset) is edited in the entry form. The walk borrowed the article
|
|
443
|
+
toolset for the block steps; a starter section with a long-form body
|
|
444
|
+
would let the canvas show them.
|
|
445
|
+
- The caret placed right after an existing `/` reopens the slash menu
|
|
446
|
+
(TipTap's suggestion re-triggers on the character before the caret).
|
|
447
|
+
Harmless; a trigger keyed to typing only would be stricter.
|
|
448
|
+
- Walk findings, all fixed the same day (69aecc6): the copied flex gap
|
|
449
|
+
landed on the editor root instead of the ProseMirror element that holds
|
|
450
|
+
the blocks, so paragraphs lost their spacing; a click on a marked image
|
|
451
|
+
revealed nothing because the reveal forced the Content tab while the
|
|
452
|
+
media projection puts the image on Media; Escape after a card's button
|
|
453
|
+
had taken the focus did not end the session.
|
|
454
|
+
|
|
455
|
+
## Follow-ups (logged 2026-09-08, admin restyle)
|
|
456
|
+
|
|
457
|
+
- Remove the temporary `technical` look once the warm restyle is decided:
|
|
458
|
+
the `TECHNICAL_*` strings (frozen tokens plus the `_SHAPE`/`_EXTRA`
|
|
459
|
+
additions), the second `cascade` call and the boot script's
|
|
460
|
+
`smoodly-look` branch in `src/admin/ui/theme.tsx`. The original look
|
|
461
|
+
stays at tag `look-technical`; restore the file from there if the
|
|
462
|
+
restyle is dropped instead.
|
|
463
|
+
- The technical block is a token snapshot, not a pixel restore: shared
|
|
464
|
+
class rules moved with the warm look, so under `data-look="technical"`
|
|
465
|
+
the inspector is flush cards with a hairline instead of one column with
|
|
466
|
+
a sticky head, tabs are stretched, inputs have a 1px (not 1.5px)
|
|
467
|
+
border, rail tiles are 42×34, and a few weights and gaps drifted. Exact
|
|
468
|
+
comparison is the tag.
|
|
469
|
+
- Signed-in walk of the warm look still pending: the sign-in screen was
|
|
470
|
+
checked in the browser pane (light and dark), the list, entry form and
|
|
471
|
+
visual editor were not — the magic-link sign-in carries a token in the
|
|
472
|
+
URL, which the pane's policy blocks. Walk them, then tune: inspector
|
|
473
|
+
width (320px here, the handoff says 296px; the field widgets need the
|
|
474
|
+
room), the canvas meta bar (the handoff has none; ours holds the locale
|
|
475
|
+
switcher), and the block-toolbar note.
|
|
476
|
+
- The handoff's `Styles` tab in the inspector is not built: it exists
|
|
477
|
+
only where a section registers styles, which is the existing `tabsFor`.
|
|
478
|
+
- Wordmark and app icon colour: the wordmark now takes `--logo-color`
|
|
479
|
+
(accent in the warm look); the rail's app icon is hidden in the warm
|
|
480
|
+
look and still brand blue in the technical one. A warm app icon is a
|
|
481
|
+
design question.
|
|
482
|
+
|
|
483
|
+
## Follow-ups (logged 2026-09-08, block nodes spec)
|
|
484
|
+
|
|
485
|
+
- Image sizing and float in a body (full width, wide, small, left/right):
|
|
486
|
+
a later additive `styles` on the image node; the site's CSS answers it.
|
|
487
|
+
- Inline elements: `inline: true` on the registration; cursor, marks and
|
|
488
|
+
selection around an inline atom are a separate node-view effort.
|
|
489
|
+
- The column context (`contexts: ["column"]`) waits for Phase 3 rows;
|
|
490
|
+
accepted and unused today.
|
|
491
|
+
- A page or entry picker for `f.link()` inside an element — the link
|
|
492
|
+
picker spec.
|
|
493
|
+
- Element copy between fields: a pasted element the target toolset
|
|
494
|
+
forbids is dropped silently; a notice would be kinder.
|
|
495
|
+
- Placeholder marking for sample-populated element fields waits for the
|
|
496
|
+
tree's own placeholder marking.
|
|
497
|
+
- An element's styles are not merged with the config's `styles.elements`
|
|
498
|
+
standard set (sections get `resolvedStyles`); add the merge when the
|
|
499
|
+
set is non-empty somewhere.
|
|
500
|
+
- Element styles are not validated on publish (sections' aren't either).
|
|
501
|
+
- `hasContent` treats an unresolved image node as content, so a body
|
|
502
|
+
whose only image was deleted from the library still passes "required".
|
|
503
|
+
- Publishing a saved version of a versioned entry: the entry form's
|
|
504
|
+
status control returns early when the target equals the current status,
|
|
505
|
+
so a new version of a published entry goes live only by toggling Draft
|
|
506
|
+
then Published (seen in the block-nodes walk; pre-existing). A "Publish
|
|
507
|
+
changes" action when the draft version is ahead of the published one.
|
|
508
|
+
- The insert position is recorded when the "+" menu opens and handed to
|
|
509
|
+
`insertContentAt`, because after the menu or the media picker takes
|
|
510
|
+
focus the refocused contenteditable placed its DOM caret at the first
|
|
511
|
+
card and the editor's selection followed it (the walk's first inserts
|
|
512
|
+
landed at the document top). The explicit position closes the hole;
|
|
513
|
+
the DOM-caret drift itself was not chased to its root.
|
|
514
|
+
- The "+" menu and the slash menu share `RichtextInsertMenu` but keep
|
|
515
|
+
separate active-row state and key handling; one keyboard model would
|
|
516
|
+
be smaller.
|
|
517
|
+
|
|
393
518
|
## Follow-ups (logged 2026-09-08, tooling session)
|
|
394
519
|
|
|
395
520
|
- **CI live job red since e09a632, 0.0.11 fresh install broken — fixed
|
|
@@ -436,8 +561,6 @@ after both are on the registry. From the repo root:
|
|
|
436
561
|
- Video transcoding and adaptive streaming: a Mux or Cloudflare Stream
|
|
437
562
|
loader behind the `media.loader` seam, if a site ever needs more than
|
|
438
563
|
an MP4 over Storage's CDN.
|
|
439
|
-
- Richtext images: the `image` node from the Phase 0 sketch, using the
|
|
440
|
-
media picker once it exists.
|
|
441
564
|
- Sweeping abandoned `pending` asset rows (an upload that began and
|
|
442
565
|
never called `finishUpload`); a periodic op or a cleanup on `list`.
|
|
443
566
|
- A private bucket with signed reads, for sites that must not expose
|
|
@@ -503,18 +626,62 @@ after both are on the registry. From the repo root:
|
|
|
503
626
|
generated (gitignored), so "templates rebuilt" is a build step, not a
|
|
504
627
|
diff.
|
|
505
628
|
|
|
629
|
+
## Follow-ups (logged 2026-09-09, admin tweaks)
|
|
630
|
+
|
|
631
|
+
The 2026-09-09 tweak round: view titles sans 18px, the entry and
|
|
632
|
+
Globals forms in one 680px column with a globe-off mark on fields that
|
|
633
|
+
are not `.localized()` (the "Shared across languages" / "In <locale>"
|
|
634
|
+
groups are gone), the image field on a 300px canvas, every locale
|
|
635
|
+
switch a segmented pill (a dropdown past three locales), Media before
|
|
636
|
+
Globals (née Shared) on the rail, richtext paragraph and card spacing,
|
|
637
|
+
element cards collapsed unless blank, and the "+" menu floating under
|
|
638
|
+
the toolbar instead of pushing the content.
|
|
639
|
+
|
|
640
|
+
- `LocaleSwitcher` in pill form has no "+ add" affordance: an absent
|
|
641
|
+
locale reads subtle and switching to it lands on the form's or the
|
|
642
|
+
editor's "doesn't exist yet · Add" pane, which adds. The dropdown form
|
|
643
|
+
(four locales and up) still adds straight from its "Add locale" group,
|
|
644
|
+
so the two forms differ by one click; a "+" inside the pill would close
|
|
645
|
+
that gap.
|
|
646
|
+
- The image field's canvas height (300px) lives twice: `CANVAS_H` in
|
|
647
|
+
`MediaWidget.tsx` sizes the frame, `.sm-media__canvas` /
|
|
648
|
+
`.sm-media__thumb` cap it in the stylesheet. One token would do.
|
|
649
|
+
- `.sm-form>*{max-width:680px}` caps the banner too; if a wide
|
|
650
|
+
field type ever appears (a table), give it an opt-out class.
|
|
651
|
+
- An element card decides "open" from its fields alone (blank = open);
|
|
652
|
+
an element whose fields are all optional and empty by design stays
|
|
653
|
+
open on every load. The richtext image card is never collapsible.
|
|
654
|
+
- The "+" menu anchors to the toolbar's right end (`right:0` in
|
|
655
|
+
`.sm-richtext__head`), which is under the "+" only while the toolbar
|
|
656
|
+
fits on one line; in the 320px inspector the toolbar wraps and the
|
|
657
|
+
menu sits below the wrapped row's end.
|
|
658
|
+
- The technical look's `TECHNICAL_SHAPE` tokens were not touched: its
|
|
659
|
+
title stays sans at `--text-xl`, its label has no mark styling beyond
|
|
660
|
+
the shared `.sm-label__mark` rule.
|
|
661
|
+
- Browser-pane sign-in (2026-09-09): the magic-link hash on
|
|
662
|
+
`/admin/...` was NOT consumed by the admin's Supabase client in the
|
|
663
|
+
pane (the login form stayed); fetching `/auth/v1/user` with the
|
|
664
|
+
access token and writing the session object to `localStorage`
|
|
665
|
+
under `smoodly-auth`, then reloading, signed in. The pane was hidden
|
|
666
|
+
during the walk, so scrolling went through `.sm-form.scrollTop` and
|
|
667
|
+
clicks through `element.click()` in `javascript_tool`.
|
|
668
|
+
|
|
506
669
|
## Follow-ups (logged 2026-09-08, richtext widget spec)
|
|
507
670
|
|
|
508
|
-
- Drag-to-resize the inspector
|
|
509
|
-
|
|
510
|
-
|
|
671
|
+
- ~~Drag-to-resize the inspector~~ — DONE 2026-09-10 (spec
|
|
672
|
+
`docs/superpowers/specs/2026-09-10-inspector-resize-design.md`):
|
|
673
|
+
`ui/ResizeHandle.tsx` writes `--inspector-w` inline on the wrapper,
|
|
674
|
+
clamped to 260px..half the row (`ui/resize.ts`), remembered under
|
|
675
|
+
`smoodly-inspector-w` and applied by the theme's boot script. Walk
|
|
676
|
+
finding: the desktop pane hides its tab on every `navigate`, and a
|
|
677
|
+
hidden tab lays out at zero width, so the handle treats a zero
|
|
678
|
+
measurement as unknown and re-measures on focus. Real drags through
|
|
679
|
+
the pane's `left_click_drag` worked (synthetic `PointerEvent`s cannot
|
|
680
|
+
satisfy `setPointerCapture`); coordinates are the screenshot frame,
|
|
681
|
+
0.625 × CSS px at the pane's 1280 width.
|
|
511
682
|
- A page and entry picker in the link popover: a link inside a richtext
|
|
512
683
|
document is a reference the refs index, the resolver and the localizer
|
|
513
684
|
cannot see (they address refs by top-level key). Its own spec.
|
|
514
|
-
- Elements in richtext: a registered Element as a TipTap node view with a
|
|
515
|
-
small form for its fields, rendered on the site by `RichText` through
|
|
516
|
-
the registry. The next spec.
|
|
517
|
-
- Images in richtext: the Phase 0 sketch's `image` node; waits for media.
|
|
518
685
|
- Escape inside any inspector control bubbles to the editor's window
|
|
519
686
|
handler and clears the block selection — the link popover stops it, a
|
|
520
687
|
textarea or the TipTap content does not. A `keydown` guard on
|
|
@@ -607,7 +774,7 @@ after both are on the registry. From the repo root:
|
|
|
607
774
|
the header nav rows read `/`, `/posts` — where the label would identify
|
|
608
775
|
the row; `itemSummary` could prefer a `label`/`title`/`heading` key
|
|
609
776
|
(walk, 2026-09-07).
|
|
610
|
-
- New page / New folder ask for the slug through `window.prompt`, which
|
|
777
|
+
- New page / New path (a folder, "path" in the UI since 2026-09-09) ask for the slug through `window.prompt`, which
|
|
611
778
|
browser automation blocks and which cannot validate as you type; an
|
|
612
779
|
inline field would do both (walk, 2026-09-07).
|
|
613
780
|
|
|
@@ -719,11 +886,40 @@ after both are on the registry. From the repo root:
|
|
|
719
886
|
`node:crypto`, etc. from a module reachable through the root would
|
|
720
887
|
regress it silently.
|
|
721
888
|
|
|
889
|
+
## Follow-ups (logged 2026-09-09)
|
|
890
|
+
|
|
891
|
+
- Cross-zone drag in the outline: each zone is its own sortable list;
|
|
892
|
+
moving a block between zones is still insert-and-remove.
|
|
893
|
+
- Nested manual collections show no grip (`canReorder` requires
|
|
894
|
+
`depth === 1`) until the entry tree UI exists — a flat index is not a
|
|
895
|
+
sibling index there.
|
|
896
|
+
- Canvas drag: the overlay should reuse `ui/sortable.ts` across the
|
|
897
|
+
iframe boundary; `useSortable` measures container children, which the
|
|
898
|
+
overlay's ghost rows can be.
|
|
899
|
+
- The Pages row menu's Move up / Move down could retire once the grip's
|
|
900
|
+
arrow keys are familiar; they stay for now as the discoverable path.
|
|
901
|
+
- The grip's arrow keys in the pages tree step one VISIBLE slot with the
|
|
902
|
+
depth clamped — among siblings in a flat run, but across a depth change
|
|
903
|
+
the step follows the clamp. Documented as is.
|
|
904
|
+
- A dragged subtree stays collapsed after the drop (`onDragStart`
|
|
905
|
+
collapses it through `toggle` and nothing re-expands it).
|
|
906
|
+
- Walk findings (2026-09-09, every surface walked signed-in; the pointer
|
|
907
|
+
path with synthetic pointer events, autoscroll in Playwright because
|
|
908
|
+
the desktop pane pauses animation frames while hidden):
|
|
909
|
+
- FIXED: the lifted row's transform grew the scroller's overflow, so
|
|
910
|
+
the bottom-edge autoscroll chased it forever and the list scrolled
|
|
911
|
+
away under the pointer; `clampOffset` now holds the transform inside
|
|
912
|
+
the rows' extent while the slot keeps following the pointer.
|
|
913
|
+
- A depth-1 row dragged to the very end of the tree with no drift
|
|
914
|
+
projects onto the last root row's child slot, which a mount or the
|
|
915
|
+
home page refuses (muted row); the editor must drift left 20px to
|
|
916
|
+
land at the root. By spec §4 (depth = own depth + drift), noted as a
|
|
917
|
+
discoverability question for the canvas-drag round.
|
|
918
|
+
- The rows are held in place until the reload lands (checked with a
|
|
919
|
+
1.2 s fetch delay): no flicker on a fast stack either.
|
|
920
|
+
|
|
722
921
|
## Follow-ups (logged 2026-09-06)
|
|
723
922
|
|
|
724
|
-
- Drag-and-drop in the Pages list — the ops it needs exist
|
|
725
|
-
(`pages.move(id, { parentId, index })`); the row menu's Move up / Move
|
|
726
|
-
down is the interim.
|
|
727
923
|
- Nested entries have no admin UI beyond `entries.move`: nothing creates
|
|
728
924
|
a child entry or shows the entry tree, and the entry form's slug prefix
|
|
729
925
|
is the collection's mount base only, not the parent chain.
|
|
@@ -817,8 +1013,6 @@ after both are on the registry. From the repo root:
|
|
|
817
1013
|
- `EditorView.tsx` is ~850 lines now that it also holds the locale switch,
|
|
818
1014
|
add and remove flows — see the 2026-09-03 follow-up below for the seams
|
|
819
1015
|
(that entry is the one to act on).
|
|
820
|
-
- The `LocaleSwitcher` is a native `<select>`; a `Segmented` with an
|
|
821
|
-
"+ add" affordance would match the Pages list.
|
|
822
1016
|
- `i18n/request.ts` returns `{ locale, messages: {} }`. next-intl 4.14.2
|
|
823
1017
|
throws `No messages found` when the provider inherits none, and the
|
|
824
1018
|
example has no UI strings of its own, so the object is empty; a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TreeNode } from "../../render.tsx";
|
|
2
2
|
import type { PlusButton } from "./overlay-geometry.ts";
|
|
3
|
-
import type { BridgeRect, ZoneRect } from "./protocol.ts";
|
|
3
|
+
import type { BridgeRect, FieldRect, ZoneRect } from "./protocol.ts";
|
|
4
4
|
export type OverlaySelection = {
|
|
5
5
|
node: TreeNode;
|
|
6
6
|
rect: BridgeRect;
|
|
@@ -8,11 +8,15 @@ export type OverlaySelection = {
|
|
|
8
8
|
shared: boolean;
|
|
9
9
|
locked: boolean;
|
|
10
10
|
};
|
|
11
|
-
export declare function CanvasOverlay({ emptyZones, hoverRect, selection, plusButtons, onPlus, onMove, onDuplicate, onRemove, }: {
|
|
11
|
+
export declare function CanvasOverlay({ emptyZones, hoverRect, selection, plusButtons, fieldHover, editing, onPlus, onMove, onDuplicate, onRemove, }: {
|
|
12
12
|
emptyZones: ZoneRect[];
|
|
13
13
|
hoverRect: BridgeRect | undefined;
|
|
14
14
|
selection: OverlaySelection | null;
|
|
15
15
|
plusButtons: PlusButton[];
|
|
16
|
+
/** The marked field under the pointer, inside the selected section (spec 2026-09-10 §2). */
|
|
17
|
+
fieldHover?: FieldRect;
|
|
18
|
+
/** The field with an active inline session. */
|
|
19
|
+
editing?: FieldRect;
|
|
16
20
|
/** A "+" was clicked, at these viewport coordinates. */
|
|
17
21
|
onPlus(button: PlusButton, clientX: number, clientY: number): void;
|
|
18
22
|
onMove(id: string, dir: -1 | 1): void;
|
|
@@ -7,33 +7,31 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
|
|
|
7
7
|
// itself never touches the iframe.
|
|
8
8
|
import { useState } from "react";
|
|
9
9
|
import { IconButton } from "../ui/primitives.js";
|
|
10
|
-
export function CanvasOverlay({ emptyZones, hoverRect, selection, plusButtons, onPlus, onMove, onDuplicate, onRemove, }) {
|
|
10
|
+
export function CanvasOverlay({ emptyZones, hoverRect, selection, plusButtons, fieldHover, editing, onPlus, onMove, onDuplicate, onRemove, }) {
|
|
11
11
|
const [hoverPlus, setHoverPlus] = useState(null);
|
|
12
|
-
return (_jsxs("div", { style: { position: "absolute", inset: 0, pointerEvents: "none" }, children: [emptyZones.map((z) => (_jsxs("div", { className: "sm-dashed
|
|
12
|
+
return (_jsxs("div", { style: { position: "absolute", inset: 0, pointerEvents: "none" }, children: [emptyZones.map((z) => (_jsxs("div", { className: "sm-dashed", style: {
|
|
13
13
|
position: "absolute", left: z.x + 12, top: z.y, width: Math.max(0, z.w - 24),
|
|
14
|
-
height: Math.max(56, z.h),
|
|
15
|
-
letterSpacing: ".04em", textTransform: "uppercase", color: "var(--gray-500)",
|
|
16
|
-
borderColor: "var(--gray-300)",
|
|
14
|
+
height: Math.max(56, z.h),
|
|
17
15
|
}, children: [z.zone, " \u00B7 empty"] }, `empty-${z.zone}`))), hoverRect && (_jsx("div", { style: {
|
|
18
16
|
position: "absolute", left: hoverRect.x, top: hoverRect.y,
|
|
19
17
|
width: hoverRect.w, height: hoverRect.h,
|
|
20
18
|
outline: "1.5px solid color-mix(in srgb, var(--selection) 45%, transparent)",
|
|
21
|
-
outlineOffset: -1.5, borderRadius: "var(--radius
|
|
22
|
-
} })), selection && (_jsxs(_Fragment, { children: [_jsx("div", { style: {
|
|
19
|
+
outlineOffset: -1.5, borderRadius: "var(--selection-radius)",
|
|
20
|
+
} })), fieldHover && !(editing && editing.node === fieldHover.node && editing.path === fieldHover.path) && (_jsx("div", { className: "sm-fieldhover", style: { position: "absolute", left: fieldHover.x, top: fieldHover.y, width: fieldHover.w, height: fieldHover.h } })), editing && (_jsx("div", { className: "sm-fieldedit", style: { position: "absolute", left: editing.x, top: editing.y, width: editing.w, height: editing.h } })), selection && (_jsxs(_Fragment, { children: [_jsx("div", { style: {
|
|
23
21
|
position: "absolute", left: selection.rect.x, top: selection.rect.y,
|
|
24
22
|
width: selection.rect.w, height: selection.rect.h,
|
|
25
23
|
outline: "var(--outline-selection)", outlineOffset: -1.5,
|
|
26
|
-
borderRadius: "var(--radius
|
|
24
|
+
borderRadius: "var(--selection-radius)",
|
|
27
25
|
} }), _jsxs("span", { className: "sm-blocktag", style: { position: "absolute", left: selection.rect.x, top: selection.rect.y }, children: [selection.title, selection.shared && " · shared", selection.locked && " · locked"] }), _jsx("div", { className: "sm-blocktoolbar", style: {
|
|
28
26
|
position: "absolute", left: selection.rect.x + selection.rect.w - 8, top: selection.rect.y + 8,
|
|
29
27
|
transform: "translateX(-100%)", pointerEvents: "auto",
|
|
30
|
-
}, children: selection.locked ? (_jsx("span", { className: "sm-
|
|
28
|
+
}, children: selection.locked ? (_jsx("span", { className: "sm-blocktoolbar__note", children: "fields only" })) : (_jsxs(_Fragment, { children: [_jsx(IconButton, { label: "Move up", tone: "inverse", onClick: () => onMove(selection.node.id, -1), children: "\u2191" }), _jsx(IconButton, { label: "Move down", tone: "inverse", onClick: () => onMove(selection.node.id, 1), children: "\u2193" }), _jsx(IconButton, { label: "Duplicate", tone: "inverse", onClick: () => onDuplicate(selection.node), children: "\u29C9" }), _jsx(IconButton, { label: "Remove", tone: "inverse", onClick: () => onRemove(selection.node.id), children: "\u00D7" })] })) })] })), plusButtons.map((p) => {
|
|
31
29
|
const key = `${p.zone}-${p.index}`;
|
|
32
30
|
const hot = hoverPlus === key;
|
|
33
|
-
return (_jsxs("div", { style: { position: "absolute", left: 0, right: 0, top: p.y }, children: [hot && _jsx("div", { className: "sm-guide", style: { margin: "0 12px" } }),
|
|
31
|
+
return (_jsxs("div", { style: { position: "absolute", left: 0, right: 0, top: p.y }, children: [hot && _jsx("div", { className: "sm-guide", style: { margin: "0 12px" } }), _jsx("div", { style: {
|
|
34
32
|
position: "absolute", left: "50%", top: 0, transform: "translate(-50%,-50%)",
|
|
35
33
|
display: "flex", flexDirection: "column", alignItems: "center", gap: 6,
|
|
36
34
|
pointerEvents: "auto",
|
|
37
|
-
}, children:
|
|
35
|
+
}, children: _jsxs("button", { className: "sm-plus", "aria-label": `Add to ${p.zone}`, onMouseEnter: () => setHoverPlus(key), onMouseLeave: () => setHoverPlus((k) => (k === key ? null : k)), onClick: (e) => onPlus(p, e.clientX, e.clientY), children: [_jsx("span", { className: "sm-plus__icon", children: "+" }), _jsx("span", { className: "sm-plus__label", children: "Add block" })] }) })] }, key));
|
|
38
36
|
})] }));
|
|
39
37
|
}
|