smoodly 0.0.12 → 0.0.14
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 +294 -40
- package/dist/admin/editor/CanvasOverlay.d.ts +6 -2
- package/dist/admin/editor/CanvasOverlay.js +9 -11
- package/dist/admin/editor/EditorView.js +135 -28
- 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/PageInspector.d.ts +21 -0
- package/dist/admin/editor/PageInspector.js +24 -0
- package/dist/admin/editor/PageSettings.d.ts +1 -3
- package/dist/admin/editor/PageSettings.js +7 -6
- package/dist/admin/editor/SeoSettings.d.ts +7 -0
- package/dist/admin/editor/SeoSettings.js +15 -0
- 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 +35 -43
- 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 +85 -19
- package/dist/admin/richtext-doc.d.ts +3 -0
- package/dist/admin/richtext-doc.js +12 -0
- package/dist/admin/serialize.d.ts +11 -0
- package/dist/admin/serialize.js +18 -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 +11 -1
- package/dist/admin/tree-ops.js +32 -1
- 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 +38 -4
- package/dist/admin/ui/primitives.js +35 -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 +527 -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 +52 -12
- package/dist/collections.d.ts +6 -5
- package/dist/config.d.ts +1 -0
- package/dist/config.js +36 -0
- package/dist/entry-store.d.ts +23 -3
- package/dist/entry-store.js +31 -5
- package/dist/field-defaults.d.ts +8 -0
- package/dist/field-defaults.js +18 -0
- package/dist/fields.d.ts +30 -1
- package/dist/fields.js +33 -3
- package/dist/image/SmoodlyImage.d.ts +4 -1
- package/dist/image/SmoodlyImage.js +2 -2
- package/dist/index.d.ts +16 -7
- package/dist/index.js +10 -4
- 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/next/index.d.ts +2 -0
- package/dist/next/index.js +1 -0
- package/dist/next/meta.d.ts +7 -3
- package/dist/next/meta.js +18 -9
- package/dist/next/page-renderer.js +1 -1
- package/dist/next/queries.d.ts +36 -0
- package/dist/next/queries.js +87 -0
- package/dist/page-tree.d.ts +3 -0
- package/dist/page-tree.js +6 -0
- package/dist/page.d.ts +11 -3
- package/dist/page.js +26 -0
- package/dist/pairing.d.ts +2 -1
- package/dist/pairing.js +2 -9
- package/dist/refs.d.ts +2 -2
- package/dist/refs.js +33 -12
- package/dist/render.d.ts +14 -7
- package/dist/render.js +4 -3
- package/dist/resolve.d.ts +12 -2
- package/dist/resolve.js +16 -7
- package/dist/revalidate.d.ts +6 -0
- package/dist/revalidate.js +6 -0
- 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/shared.d.ts +7 -7
- package/dist/site.d.ts +25 -1
- package/dist/site.js +89 -22
- package/dist/store.d.ts +7 -1
- package/dist/store.js +13 -4
- package/dist/supabase-entry-store.d.ts +1 -0
- package/dist/supabase-entry-store.js +34 -11
- package/dist/supabase-store.d.ts +1 -0
- package/dist/supabase-store.js +14 -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,13 +249,18 @@ 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
|
-
selected, page settings (`admin/editor/
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
selected, page settings (`admin/editor/PageInspector.tsx`, spec
|
|
259
|
+
2026-09-10 page fields: **SEO & Social** — title, description, social
|
|
260
|
+
image, hide from search engines, under the tree's `meta` — and
|
|
261
|
+
**Settings** — the ACTIVE locale's title and slug through `pages.rename`,
|
|
262
|
+
the template's page fields under the tree's `fields`, remove from
|
|
263
|
+
locale). The entry form prefixes its slug field with the collection's
|
|
257
264
|
mount path. The serialized
|
|
258
265
|
registry carries `collections[].path`/`depth`, `pages[].slugs` and
|
|
259
266
|
`pageDepth`, and `defineConfig` validates `homePageSlug` — a real
|
|
@@ -274,12 +281,27 @@ Implemented:
|
|
|
274
281
|
is the two in sequence — `locate` then `load` — for a caller that just
|
|
275
282
|
wants the hit. A folder (null template), an
|
|
276
283
|
unpublished record outside draft mode, or a missing one is `null`.
|
|
277
|
-
On top of it the
|
|
278
|
-
|
|
279
|
-
`{
|
|
280
|
-
`
|
|
281
|
-
`
|
|
282
|
-
|
|
284
|
+
On top of it the content API for menus, sitemaps and custom routes,
|
|
285
|
+
published-only unless `draft: true` (spec 2026-09-11):
|
|
286
|
+
`getPagesTree({ locale, draft? })` (nested
|
|
287
|
+
`{ id, title, path, hasPage, fields, children }`), `getPage(pathOrId)`,
|
|
288
|
+
`getEntries(collection, { locale, parent?, order?, where?, limit?, draft? })`
|
|
289
|
+
— `where` is equality / any-of on shared fields and `limit` a SQL limit,
|
|
290
|
+
both run by the store — `getEntry(collection, idOrPath)` (an id, a
|
|
291
|
+
path, or a bare slug naming a root entry) and `getEntriesTree(collection)`.
|
|
292
|
+
In the app, read through `q` (next bullet) rather than the site layer.
|
|
293
|
+
- `createSmoodlyQueries` (`smoodly/next`, spec 2026-09-11) — the read
|
|
294
|
+
API the scaffold exports as `q`: `getPageTree({ locale })`,
|
|
295
|
+
`getPage(pathOrId)`, `getEntries("articles", { where, order, limit,
|
|
296
|
+
parent })`, `getEntry("articles", idOrPath)` (an id or a path — a bare
|
|
297
|
+
slug is refused, since its unit would sit under a tag no op fires) and
|
|
298
|
+
`getShared("header")`. Typed by the registry (names are checked, fields
|
|
299
|
+
typed), the live draft inside the editor preview, and outside it one
|
|
300
|
+
`unstable_cache` unit per distinct call under a tag the admin ops
|
|
301
|
+
expire: `pages:tree` (every page op that changes the listing),
|
|
302
|
+
`entries:<collection>` (every entry write), `page:<id>`, `entry:<id>`,
|
|
303
|
+
`shared:<name>`. `filterPageTree(items, keep)` prunes a tree for a nav
|
|
304
|
+
(a dropped parent drops its subtree).
|
|
283
305
|
- `smoodly/next` — `createPageRenderer`, the site-route seam over that
|
|
284
306
|
layer, bound once by the app like `createAdminOpHandler`. The two route
|
|
285
307
|
files call `renderSmoodlyPath(segments, { locale?, searchParams? })` and
|
|
@@ -320,6 +342,10 @@ Implemented:
|
|
|
320
342
|
because Next loads `next.config.ts` in plain Node, where anything that
|
|
321
343
|
reaches `next/image` fails to resolve (next@16 has no `exports` map);
|
|
322
344
|
`smoodly/image` still re-exports the helper for code the bundler sees.
|
|
345
|
+
- `smoodly/richtext` — the bound richtext renderer, `createRichText({
|
|
346
|
+
elements, sizes })`: the root `RichText` with the project's elements and
|
|
347
|
+
body images through `<SmoodlyImage>`. Its own entry for the same reason
|
|
348
|
+
as `smoodly/image`: it reaches `next/image`.
|
|
323
349
|
|
|
324
350
|
- Locked zones are usable: `fillLockedZones` (a pure tree op) materializes
|
|
325
351
|
the one node of every empty locked zone from the component's `sample`
|
|
@@ -332,7 +358,8 @@ Implemented:
|
|
|
332
358
|
registers a visual item, `smoodly.shared({ name, title, fields })` an
|
|
333
359
|
object item; one registration materializes as a singleton entry in
|
|
334
360
|
every supported locale. `site.getShared(item, { locale })` and the Next
|
|
335
|
-
glue's `
|
|
361
|
+
glue's `q.getShared("footer", { locale })` fetch it code-side (the
|
|
362
|
+
renderer's `getSmoodlyShared(item, { locale })` still works); the
|
|
336
363
|
editor places a visual item in a zone from the "+" picker as a linked
|
|
337
364
|
`{ type: "shared", fields: { item, ref } }` node the resolve pass
|
|
338
365
|
rewrites into the section, and `z.locked(footer)` binds a zone to one.
|
|
@@ -362,11 +389,20 @@ Implemented:
|
|
|
362
389
|
a toolset forbids survives every round trip. Links carry `href` only. An
|
|
363
390
|
empty document is missing for a required field. The widget emits plain
|
|
364
391
|
JSON: ProseMirror's node attrs have a null prototype, which a server
|
|
365
|
-
action cannot serialize.
|
|
392
|
+
action cannot serialize. Block nodes (spec 2026-09-08 block nodes): an
|
|
393
|
+
`image` node (media library, caption, alt override) and one generic
|
|
394
|
+
`element` node for every registered Element, always in the schema,
|
|
395
|
+
inserted through the toolbar's "+" or a `/` menu, edited as cards (the
|
|
396
|
+
element card renders its fields through `FieldWidget`, nested richtext
|
|
397
|
+
included). `RichText` takes `elements` and an `image` render prop;
|
|
398
|
+
`smoodly/richtext` exports `createRichText` bound over `<SmoodlyImage>`.
|
|
399
|
+
The media and refs walks descend into documents against the element
|
|
400
|
+
schemas (`elements` beside `sections` in the store, resolve and site
|
|
401
|
+
options).
|
|
366
402
|
|
|
367
|
-
Not yet:
|
|
368
|
-
|
|
369
|
-
|
|
403
|
+
Not yet: canvas drag-and-drop (list fields, the outline, manual
|
|
404
|
+
collections and the pages tree drag since 2026-09-09 — spec
|
|
405
|
+
`docs/superpowers/specs/2026-09-09-drag-and-drop-design.md`), undo/redo, sample/placeholder content
|
|
370
406
|
marking, per-locale section visibility conditions (OQ #3 — each locale
|
|
371
407
|
now has its own tree, so a section's fields are already per locale; what
|
|
372
408
|
is open is hiding a section by a locale-dependent condition).
|
|
@@ -390,6 +426,160 @@ after both are on the registry. From the repo root:
|
|
|
390
426
|
`prepublishOnly` rebuilds each package. The Claude Code skill
|
|
391
427
|
`.claude/skills/releasing-smoodly` carries the same order with its checks.
|
|
392
428
|
|
|
429
|
+
## Follow-ups (logged 2026-09-11, queries spec)
|
|
430
|
+
|
|
431
|
+
- `where` on localized fields (a join on the locale row), ranges and
|
|
432
|
+
negation; any-of over an array-valued field (any-of compares the text
|
|
433
|
+
form of the field, so it covers selects, refs and numbers; on a
|
|
434
|
+
checkboxes, refList or list field `site.getEntries` refuses it rather
|
|
435
|
+
than match nothing — both adapters agree on the text form, so a
|
|
436
|
+
one-element array never matches its element).
|
|
437
|
+
- `where` filters a versioned collection's LIVE row, not its published
|
|
438
|
+
snapshot: an entry whose published version had `kind: "x"` and whose
|
|
439
|
+
draft changed it to `"y"` drops out of `where: { kind: "x" }` before
|
|
440
|
+
the change is published. The published gate is per locale row, the
|
|
441
|
+
filter is on the node's shared fields. Fix by filtering the snapshot
|
|
442
|
+
(a join on the version row) or by documenting that a versioned
|
|
443
|
+
collection's shared fields are live for filtering.
|
|
444
|
+
- A GIN index on `entries.fields` when a customer's collection is large
|
|
445
|
+
enough to need it; additive, no migration.
|
|
446
|
+
- OQ 26: whether page units should carry `pages:tree` too, replacing
|
|
447
|
+
the neighbour list.
|
|
448
|
+
- Scripts and the seed write through the stores and expire nothing; a
|
|
449
|
+
`revalidate` hook for out-of-band writers.
|
|
450
|
+
- `getEntriesTree` has no `q` counterpart yet; add one when a site
|
|
451
|
+
needs a nested collection nav.
|
|
452
|
+
- `getEntry` by id goes through `site.getEntry`, which lists the whole
|
|
453
|
+
collection to find one row; a primary-key read first would be cheaper
|
|
454
|
+
on the uncached path (draft mode, first miss).
|
|
455
|
+
|
|
456
|
+
## Follow-ups (logged 2026-09-10, page fields and SEO spec)
|
|
457
|
+
|
|
458
|
+
- SEO for entry pages: entries have no `meta`; a `titleField`-based
|
|
459
|
+
title and a description field are the next step.
|
|
460
|
+
- A `styles` block on pages, when a case needs presentation separate
|
|
461
|
+
from content.
|
|
462
|
+
- Refs in page fields, once the refs index walks them.
|
|
463
|
+
- A canonical URL field and a search-result preview snippet in the
|
|
464
|
+
SEO & Social tab.
|
|
465
|
+
- Configurable SEO set (`meta: { seo: { twitter: true } }`) if the four
|
|
466
|
+
values prove too few.
|
|
467
|
+
- Link fields are raw: an image in a page field reaches a nav link as
|
|
468
|
+
`{ asset, alt }`, not a URL. Resolve on demand if a nav ever wants one.
|
|
469
|
+
- The social image URL goes through the default `next` loader at 1200px;
|
|
470
|
+
the cloud's transform loader (OQ 28) should serve a real 1200×630.
|
|
471
|
+
- Page-field labels derive from the key (`showInNavigation` → "Show In
|
|
472
|
+
Navigation"), as every field's do; a `.label()` builder option would
|
|
473
|
+
read better for long keys.
|
|
474
|
+
|
|
475
|
+
## Follow-ups (logged 2026-09-10, inline editing spec)
|
|
476
|
+
|
|
477
|
+
- Shared placements ignore field clicks: their fields live in the shared
|
|
478
|
+
item under Globals, which the page editor cannot patch. The starter's
|
|
479
|
+
Header and Footer carry marks already; inline editing of shared content
|
|
480
|
+
needs the shared form's ops behind the canvas.
|
|
481
|
+
- Richtext instant write from the inspector: the pure `RichText` render
|
|
482
|
+
swapped into the container while the widget is typed in.
|
|
483
|
+
- Item-level chrome in the canvas ("Key points · 3 of 4" on hover, add,
|
|
484
|
+
remove, reorder in place, later drag within a list): the item marks are
|
|
485
|
+
the hook.
|
|
486
|
+
- Placeholder and validation marks on the page (the sample-sourced
|
|
487
|
+
placeholder treatment, "required, empty" outlines) wait for the tree's
|
|
488
|
+
placeholder marking.
|
|
489
|
+
- A coverage hint for developers: the admin knows a section's text fields
|
|
490
|
+
and could list the ones without a mark.
|
|
491
|
+
- AI verbs ("rewrite this heading") need a field under the cursor, which
|
|
492
|
+
a mark is.
|
|
493
|
+
- A cross-origin admin needs the bridge-side editing variant (spec §3).
|
|
494
|
+
- Direct-child spacing rules on a richtext container (Tailwind `space-y-*`,
|
|
495
|
+
`.prose > p`) do not reach the canvas editor's blocks during a session;
|
|
496
|
+
flex and grid gaps are copied (onto the ProseMirror root since the
|
|
497
|
+
walk, 69aecc6). Documented, not fixed.
|
|
498
|
+
- The text session's Enter commits: a plain text field has no line breaks.
|
|
499
|
+
A multi-line text control would be its own descriptor.
|
|
500
|
+
- The floating toolbar flips below the caret only near the top of the
|
|
501
|
+
DOCUMENT (where the page box clips it); it scrolls with the content, so
|
|
502
|
+
a caret at the top of the scrolled canvas has its toolbar out of view
|
|
503
|
+
until the editor scrolls. A flip keyed to the scroller's viewport would
|
|
504
|
+
fix it.
|
|
505
|
+
- No starter section gives the canvas a richtext with blocks: the Image
|
|
506
|
+
and text body is marks-only, and the article body (the `article`
|
|
507
|
+
toolset) is edited in the entry form. The walk borrowed the article
|
|
508
|
+
toolset for the block steps; a starter section with a long-form body
|
|
509
|
+
would let the canvas show them.
|
|
510
|
+
- The caret placed right after an existing `/` reopens the slash menu
|
|
511
|
+
(TipTap's suggestion re-triggers on the character before the caret).
|
|
512
|
+
Harmless; a trigger keyed to typing only would be stricter.
|
|
513
|
+
- Walk findings, all fixed the same day (69aecc6): the copied flex gap
|
|
514
|
+
landed on the editor root instead of the ProseMirror element that holds
|
|
515
|
+
the blocks, so paragraphs lost their spacing; a click on a marked image
|
|
516
|
+
revealed nothing because the reveal forced the Content tab while the
|
|
517
|
+
media projection puts the image on Media; Escape after a card's button
|
|
518
|
+
had taken the focus did not end the session.
|
|
519
|
+
|
|
520
|
+
## Follow-ups (logged 2026-09-08, admin restyle)
|
|
521
|
+
|
|
522
|
+
- Remove the temporary `technical` look once the warm restyle is decided:
|
|
523
|
+
the `TECHNICAL_*` strings (frozen tokens plus the `_SHAPE`/`_EXTRA`
|
|
524
|
+
additions), the second `cascade` call and the boot script's
|
|
525
|
+
`smoodly-look` branch in `src/admin/ui/theme.tsx`. The original look
|
|
526
|
+
stays at tag `look-technical`; restore the file from there if the
|
|
527
|
+
restyle is dropped instead.
|
|
528
|
+
- The technical block is a token snapshot, not a pixel restore: shared
|
|
529
|
+
class rules moved with the warm look, so under `data-look="technical"`
|
|
530
|
+
the inspector is flush cards with a hairline instead of one column with
|
|
531
|
+
a sticky head, tabs are stretched, inputs have a 1px (not 1.5px)
|
|
532
|
+
border, rail tiles are 42×34, and a few weights and gaps drifted. Exact
|
|
533
|
+
comparison is the tag.
|
|
534
|
+
- Signed-in walk of the warm look still pending: the sign-in screen was
|
|
535
|
+
checked in the browser pane (light and dark), the list, entry form and
|
|
536
|
+
visual editor were not — the magic-link sign-in carries a token in the
|
|
537
|
+
URL, which the pane's policy blocks. Walk them, then tune: inspector
|
|
538
|
+
width (320px here, the handoff says 296px; the field widgets need the
|
|
539
|
+
room), the canvas meta bar (the handoff has none; ours holds the locale
|
|
540
|
+
switcher), and the block-toolbar note.
|
|
541
|
+
- The handoff's `Styles` tab in the inspector is not built: it exists
|
|
542
|
+
only where a section registers styles, which is the existing `tabsFor`.
|
|
543
|
+
- Wordmark and app icon colour: the wordmark now takes `--logo-color`
|
|
544
|
+
(accent in the warm look); the rail's app icon is hidden in the warm
|
|
545
|
+
look and still brand blue in the technical one. A warm app icon is a
|
|
546
|
+
design question.
|
|
547
|
+
|
|
548
|
+
## Follow-ups (logged 2026-09-08, block nodes spec)
|
|
549
|
+
|
|
550
|
+
- Image sizing and float in a body (full width, wide, small, left/right):
|
|
551
|
+
a later additive `styles` on the image node; the site's CSS answers it.
|
|
552
|
+
- Inline elements: `inline: true` on the registration; cursor, marks and
|
|
553
|
+
selection around an inline atom are a separate node-view effort.
|
|
554
|
+
- The column context (`contexts: ["column"]`) waits for Phase 3 rows;
|
|
555
|
+
accepted and unused today.
|
|
556
|
+
- A page or entry picker for `f.link()` inside an element — the link
|
|
557
|
+
picker spec.
|
|
558
|
+
- Element copy between fields: a pasted element the target toolset
|
|
559
|
+
forbids is dropped silently; a notice would be kinder.
|
|
560
|
+
- Placeholder marking for sample-populated element fields waits for the
|
|
561
|
+
tree's own placeholder marking.
|
|
562
|
+
- An element's styles are not merged with the config's `styles.elements`
|
|
563
|
+
standard set (sections get `resolvedStyles`); add the merge when the
|
|
564
|
+
set is non-empty somewhere.
|
|
565
|
+
- Element styles are not validated on publish (sections' aren't either).
|
|
566
|
+
- `hasContent` treats an unresolved image node as content, so a body
|
|
567
|
+
whose only image was deleted from the library still passes "required".
|
|
568
|
+
- Publishing a saved version of a versioned entry: the entry form's
|
|
569
|
+
status control returns early when the target equals the current status,
|
|
570
|
+
so a new version of a published entry goes live only by toggling Draft
|
|
571
|
+
then Published (seen in the block-nodes walk; pre-existing). A "Publish
|
|
572
|
+
changes" action when the draft version is ahead of the published one.
|
|
573
|
+
- The insert position is recorded when the "+" menu opens and handed to
|
|
574
|
+
`insertContentAt`, because after the menu or the media picker takes
|
|
575
|
+
focus the refocused contenteditable placed its DOM caret at the first
|
|
576
|
+
card and the editor's selection followed it (the walk's first inserts
|
|
577
|
+
landed at the document top). The explicit position closes the hole;
|
|
578
|
+
the DOM-caret drift itself was not chased to its root.
|
|
579
|
+
- The "+" menu and the slash menu share `RichtextInsertMenu` but keep
|
|
580
|
+
separate active-row state and key handling; one keyboard model would
|
|
581
|
+
be smaller.
|
|
582
|
+
|
|
393
583
|
## Follow-ups (logged 2026-09-08, tooling session)
|
|
394
584
|
|
|
395
585
|
- **CI live job red since e09a632, 0.0.11 fresh install broken — fixed
|
|
@@ -436,8 +626,6 @@ after both are on the registry. From the repo root:
|
|
|
436
626
|
- Video transcoding and adaptive streaming: a Mux or Cloudflare Stream
|
|
437
627
|
loader behind the `media.loader` seam, if a site ever needs more than
|
|
438
628
|
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
629
|
- Sweeping abandoned `pending` asset rows (an upload that began and
|
|
442
630
|
never called `finishUpload`); a periodic op or a cleanup on `list`.
|
|
443
631
|
- A private bucket with signed reads, for sites that must not expose
|
|
@@ -503,18 +691,62 @@ after both are on the registry. From the repo root:
|
|
|
503
691
|
generated (gitignored), so "templates rebuilt" is a build step, not a
|
|
504
692
|
diff.
|
|
505
693
|
|
|
694
|
+
## Follow-ups (logged 2026-09-09, admin tweaks)
|
|
695
|
+
|
|
696
|
+
The 2026-09-09 tweak round: view titles sans 18px, the entry and
|
|
697
|
+
Globals forms in one 680px column with a globe-off mark on fields that
|
|
698
|
+
are not `.localized()` (the "Shared across languages" / "In <locale>"
|
|
699
|
+
groups are gone), the image field on a 300px canvas, every locale
|
|
700
|
+
switch a segmented pill (a dropdown past three locales), Media before
|
|
701
|
+
Globals (née Shared) on the rail, richtext paragraph and card spacing,
|
|
702
|
+
element cards collapsed unless blank, and the "+" menu floating under
|
|
703
|
+
the toolbar instead of pushing the content.
|
|
704
|
+
|
|
705
|
+
- `LocaleSwitcher` in pill form has no "+ add" affordance: an absent
|
|
706
|
+
locale reads subtle and switching to it lands on the form's or the
|
|
707
|
+
editor's "doesn't exist yet · Add" pane, which adds. The dropdown form
|
|
708
|
+
(four locales and up) still adds straight from its "Add locale" group,
|
|
709
|
+
so the two forms differ by one click; a "+" inside the pill would close
|
|
710
|
+
that gap.
|
|
711
|
+
- The image field's canvas height (300px) lives twice: `CANVAS_H` in
|
|
712
|
+
`MediaWidget.tsx` sizes the frame, `.sm-media__canvas` /
|
|
713
|
+
`.sm-media__thumb` cap it in the stylesheet. One token would do.
|
|
714
|
+
- `.sm-form>*{max-width:680px}` caps the banner too; if a wide
|
|
715
|
+
field type ever appears (a table), give it an opt-out class.
|
|
716
|
+
- An element card decides "open" from its fields alone (blank = open);
|
|
717
|
+
an element whose fields are all optional and empty by design stays
|
|
718
|
+
open on every load. The richtext image card is never collapsible.
|
|
719
|
+
- The "+" menu anchors to the toolbar's right end (`right:0` in
|
|
720
|
+
`.sm-richtext__head`), which is under the "+" only while the toolbar
|
|
721
|
+
fits on one line; in the 320px inspector the toolbar wraps and the
|
|
722
|
+
menu sits below the wrapped row's end.
|
|
723
|
+
- The technical look's `TECHNICAL_SHAPE` tokens were not touched: its
|
|
724
|
+
title stays sans at `--text-xl`, its label has no mark styling beyond
|
|
725
|
+
the shared `.sm-label__mark` rule.
|
|
726
|
+
- Browser-pane sign-in (2026-09-09): the magic-link hash on
|
|
727
|
+
`/admin/...` was NOT consumed by the admin's Supabase client in the
|
|
728
|
+
pane (the login form stayed); fetching `/auth/v1/user` with the
|
|
729
|
+
access token and writing the session object to `localStorage`
|
|
730
|
+
under `smoodly-auth`, then reloading, signed in. The pane was hidden
|
|
731
|
+
during the walk, so scrolling went through `.sm-form.scrollTop` and
|
|
732
|
+
clicks through `element.click()` in `javascript_tool`.
|
|
733
|
+
|
|
506
734
|
## Follow-ups (logged 2026-09-08, richtext widget spec)
|
|
507
735
|
|
|
508
|
-
- Drag-to-resize the inspector
|
|
509
|
-
|
|
510
|
-
|
|
736
|
+
- ~~Drag-to-resize the inspector~~ — DONE 2026-09-10 (spec
|
|
737
|
+
`docs/superpowers/specs/2026-09-10-inspector-resize-design.md`):
|
|
738
|
+
`ui/ResizeHandle.tsx` writes `--inspector-w` inline on the wrapper,
|
|
739
|
+
clamped to 260px..half the row (`ui/resize.ts`), remembered under
|
|
740
|
+
`smoodly-inspector-w` and applied by the theme's boot script. Walk
|
|
741
|
+
finding: the desktop pane hides its tab on every `navigate`, and a
|
|
742
|
+
hidden tab lays out at zero width, so the handle treats a zero
|
|
743
|
+
measurement as unknown and re-measures on focus. Real drags through
|
|
744
|
+
the pane's `left_click_drag` worked (synthetic `PointerEvent`s cannot
|
|
745
|
+
satisfy `setPointerCapture`); coordinates are the screenshot frame,
|
|
746
|
+
0.625 × CSS px at the pane's 1280 width.
|
|
511
747
|
- A page and entry picker in the link popover: a link inside a richtext
|
|
512
748
|
document is a reference the refs index, the resolver and the localizer
|
|
513
749
|
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
750
|
- Escape inside any inspector control bubbles to the editor's window
|
|
519
751
|
handler and clears the block selection — the link popover stops it, a
|
|
520
752
|
textarea or the TipTap content does not. A `keydown` guard on
|
|
@@ -546,9 +778,6 @@ after both are on the registry. From the repo root:
|
|
|
546
778
|
in the single-language scaffold). Adding `locale` to `PageContext` and
|
|
547
779
|
`EntryPageProps` would remove that dependency and let sections that
|
|
548
780
|
format dates stay synchronous.
|
|
549
|
-
- The content API is published-only, so the Articles index in the editor
|
|
550
|
-
canvas lists published articles while the hero above it is the draft.
|
|
551
|
-
A draft-aware `getEntries` for draft mode is the fix if that confuses.
|
|
552
781
|
- The image widget edits the URL only; `alt` is set by the seed and cannot
|
|
553
782
|
be changed in the admin.
|
|
554
783
|
- A per-locale collection path or fixed slug must name the default locale;
|
|
@@ -607,7 +836,7 @@ after both are on the registry. From the repo root:
|
|
|
607
836
|
the header nav rows read `/`, `/posts` — where the label would identify
|
|
608
837
|
the row; `itemSummary` could prefer a `label`/`title`/`heading` key
|
|
609
838
|
(walk, 2026-09-07).
|
|
610
|
-
- New page / New folder ask for the slug through `window.prompt`, which
|
|
839
|
+
- New page / New path (a folder, "path" in the UI since 2026-09-09) ask for the slug through `window.prompt`, which
|
|
611
840
|
browser automation blocks and which cannot validate as you type; an
|
|
612
841
|
inline field would do both (walk, 2026-09-07).
|
|
613
842
|
|
|
@@ -719,16 +948,43 @@ after both are on the registry. From the repo root:
|
|
|
719
948
|
`node:crypto`, etc. from a module reachable through the root would
|
|
720
949
|
regress it silently.
|
|
721
950
|
|
|
951
|
+
## Follow-ups (logged 2026-09-09)
|
|
952
|
+
|
|
953
|
+
- Cross-zone drag in the outline: each zone is its own sortable list;
|
|
954
|
+
moving a block between zones is still insert-and-remove.
|
|
955
|
+
- Nested manual collections show no grip (`canReorder` requires
|
|
956
|
+
`depth === 1`) until the entry tree UI exists — a flat index is not a
|
|
957
|
+
sibling index there.
|
|
958
|
+
- Canvas drag: the overlay should reuse `ui/sortable.ts` across the
|
|
959
|
+
iframe boundary; `useSortable` measures container children, which the
|
|
960
|
+
overlay's ghost rows can be.
|
|
961
|
+
- The Pages row menu's Move up / Move down could retire once the grip's
|
|
962
|
+
arrow keys are familiar; they stay for now as the discoverable path.
|
|
963
|
+
- The grip's arrow keys in the pages tree step one VISIBLE slot with the
|
|
964
|
+
depth clamped — among siblings in a flat run, but across a depth change
|
|
965
|
+
the step follows the clamp. Documented as is.
|
|
966
|
+
- A dragged subtree stays collapsed after the drop (`onDragStart`
|
|
967
|
+
collapses it through `toggle` and nothing re-expands it).
|
|
968
|
+
- Walk findings (2026-09-09, every surface walked signed-in; the pointer
|
|
969
|
+
path with synthetic pointer events, autoscroll in Playwright because
|
|
970
|
+
the desktop pane pauses animation frames while hidden):
|
|
971
|
+
- FIXED: the lifted row's transform grew the scroller's overflow, so
|
|
972
|
+
the bottom-edge autoscroll chased it forever and the list scrolled
|
|
973
|
+
away under the pointer; `clampOffset` now holds the transform inside
|
|
974
|
+
the rows' extent while the slot keeps following the pointer.
|
|
975
|
+
- A depth-1 row dragged to the very end of the tree with no drift
|
|
976
|
+
projects onto the last root row's child slot, which a mount or the
|
|
977
|
+
home page refuses (muted row); the editor must drift left 20px to
|
|
978
|
+
land at the root. By spec §4 (depth = own depth + drift), noted as a
|
|
979
|
+
discoverability question for the canvas-drag round.
|
|
980
|
+
- The rows are held in place until the reload lands (checked with a
|
|
981
|
+
1.2 s fetch delay): no flicker on a fast stack either.
|
|
982
|
+
|
|
722
983
|
## Follow-ups (logged 2026-09-06)
|
|
723
984
|
|
|
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
985
|
- Nested entries have no admin UI beyond `entries.move`: nothing creates
|
|
728
986
|
a child entry or shows the entry tree, and the entry form's slug prefix
|
|
729
987
|
is the collection's mount base only, not the parent chain.
|
|
730
|
-
- SEO/meta fields in page settings — DESIGN.md OQ #17. Title and slug per
|
|
731
|
-
locale shipped; nothing writes `PageTree.meta` yet.
|
|
732
988
|
- `pages.get` makes four sequential store round trips for a non-root page
|
|
733
989
|
(`getPage`, `getDraftTree`, `pathsOf`, and the parent read behind
|
|
734
990
|
`parentLocales`; three for a root, which has no parent to read), and the
|
|
@@ -817,8 +1073,6 @@ after both are on the registry. From the repo root:
|
|
|
817
1073
|
- `EditorView.tsx` is ~850 lines now that it also holds the locale switch,
|
|
818
1074
|
add and remove flows — see the 2026-09-03 follow-up below for the seams
|
|
819
1075
|
(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
1076
|
- `i18n/request.ts` returns `{ locale, messages: {} }`. next-intl 4.14.2
|
|
823
1077
|
throws `No messages found` when the provider inherits none, and the
|
|
824
1078
|
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
|
}
|