Commit Graph
203 Commits
Author SHA1 Message Date
shadowdaoandClaude Opus 5 024f9fdd46 feat(site-builder): stamp git sha + date into the editor bundle
package.json's version is hand-maintained and never changes between
builds, so a bug report can't identify which bundle produced it.
Vite's `define` injects __EDITOR_BUILD__ (short git SHA + build date)
at compile time; editorBuild() in build-stamp.ts is the only safe way
to read it, falling back to 'dev' since vitest does not apply Vite's
`define` and the identifier is otherwise undeclared. The execSync
call falls back to 'nogit' when building outside a git checkout
(release tarballs), verified by building from a directory with no
git ancestry at all.

Also wires editorBuild() into a startup console.log in main.tsx --
without any reference to it, Vite tree-shakes the unused module out
of the bundle entirely and __EDITOR_BUILD__ never gets substituted,
silently leaving every bug report saying 'dev'. Task 19 will add the
real call site when it assembles the report payload.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 10:59:38 -07:00
shadowdaoandClaude Opus 5 fc8918c1f9 fix(site-builder): stop the console-error marker from re-deriving "original" from a live wrapper
Review found a gap in the idempotency marker added for Task 16: if
external code wraps our patched console.error between two of our own
installs, the marker sees an unmarked function and treats it as
virgin, capturing the external wrapper itself as "the original".
That both double-records (the old patch is still reachable inside the
wrapper's closure) and makes __resetConsoleErrorBuffer() restore to
the wrapper instead of the real original.

Fix: stash the true original exactly once, directly on the `console`
object (not module scope, so it survives HMR too), and always
re-wrap that stashed reference rather than whatever console.error
currently is. Reinstalling after an external wrap now discards that
wrapper instead of guessing whether it still chains to us -- a
deliberate, documented trade-off, since there is no safe way to tell
those two cases apart from the outside.

Also: window error/rejection listeners now catch exceptions from a
hostile e.reason the same way the console.error patch already did,
and the module doc comment now notes the known HMR buffer-orphan
wrinkle. Adds two tests covering the external-wrapper and
module-re-execution scenarios; both were mutation-verified to fail
against the prior implementation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 10:51:08 -07:00
shadowdaoandClaude Opus 5 9be71e8fd1 feat(site-builder): capture recent console errors for issue reports
Ring buffer of the most recent console.error/window-error messages
(20 max, 500 chars each, message text only) for Task 19's report
payload. installConsoleErrorBuffer() is idempotent via a marker
stamped on the patched console.error itself (not just a module-scoped
flag), so React 18 StrictMode double-invocation or HMR re-running this
module's top level can't wrap an already-patched console.error and
build a growing chain. The patch always chains to the original.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 10:37:32 -07:00
shadowdaoandClaude Opus 5 aba0d187d7 fix(site-builder): make Reset Entire Site's guard load-bearing in the handler
Two Important review findings on the previous commit (1a01834):

1. handleResetSite ran unconditionally -- the confirm button's `disabled`
   attribute was the only thing standing between a mismatched/empty typed
   value and a full site wipe. Extracted the match check into a single
   exported pure predicate, siteResetConfirmMatches(typed, domain), used
   for the button's disabled/cursor/opacity (previously three duplicated
   inline comparisons) AND as the first line of handleResetSite itself,
   which now returns early if it doesn't hold. An empty domain is rejected
   outright (`!!domain &&` short-circuits) so the guard holds even if the
   handler were ever reached with no configured domain, independent of the
   entry point being hidden.

2. The dialog said "design tokens" but resetToDefaults() also wipes
   headCode (analytics/search-console/third-party scripts) and favicon --
   neither is one of the 17 documented design properties, so a user had no
   reason to read them as included. Copy now names both explicitly.
   Re-verified every remaining claim in the paragraph against what the
   handler actually does (page/header/footer replacement, no undo, no
   publish call, images untouched, 30000ms auto-save) -- all still hold.

Verified load-bearing by temporarily reverting each guard in place (no git
stash -- shared across worktrees/sessions per review feedback) and
confirming the corresponding test fails: dropping the !!domain check broke
the empty-domain unit test; removing the handleResetSite check broke a new
test that invokes the confirm button's React onClick directly (bypassing
both the disabled attribute and react-dom's own disabled-click suppression,
which independent investigation confirmed blocks a plain DOM `.disabled =
false; .click()`/dispatchEvent bypass -- pulling onClick off the element's
stashed __reactProps$ key was the only way to actually exercise the
handler's own guard).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 10:30:20 -07:00
shadowdaoandClaude Opus 5 1a01834068 feat(site-builder): add domain-confirmed Reset Entire Site
Adds a danger-zone escape hatch below SiteDesignPanel's existing "Reset to
Defaults": blanks every page down to one empty Home, blanks the header and
footer, and resets all design tokens. Guarded by typing the exact site
domain to arm the confirm button.

The brief's setHeader('')/setFooter('') calls were wrong -- both take a
SerializedTreeNode (same tree shape as replaceAllPages), not a craft-state
string; passing '' would have hit treeToCraftState's sanitizeAiTree and
silently fallen back to a generic div-shaped empty canvas instead of a
proper header/footer. Built BLANK_HEADER_TREE/BLANK_FOOTER_TREE (tagged
header/footer) alongside the brief's page tree, and dropped the brief's
literal's extraneous flat-state fields (isCanvas/displayName/custom/hidden/
linkedNodes) that made it need an `as any` cast -- sanitizeAiTree/
flattenTreeForCraft only ever read type/props/nodes.

In standalone mode (no WHP_CONFIG) siteDomain is '', so the entire danger
zone -- not just the button -- is hidden behind `siteDomain &&`, closing off
the empty-string-trivially-matches guard bypass.

Dialog copy states the reset is undoable... is NOT undoable, and that
auto-save (confirmed exactly 30s via TopBar.tsx's setInterval) turns the
blank canvas into the saved draft shortly after, so no false safety-net
claim is made.

Verified load-bearing: reverted the implementation via git stash and
confirmed 3 of 4 new tests fail (the 4th, an absence-only standalone-mode
check, passed vacuously on first draft -- rewritten into a same-test
contrast against the non-standalone case, which does fail on revert).
Split into a second mock-free integration test file
(SiteDesignPanel.reset.integration.test.tsx) after vi.mock's file-scoped
hoisting made a same-file vi.doUnmock silently keep using the mocks --
it exercises the real PageProvider/SiteDesignProvider/treeToCraftState
pipeline end to end via the real editorHarness.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 10:17:53 -07:00
shadowdaoandClaude Opus 5 bfcf6278e9 feat(site-builder): add Reset Page to the Pages panel
Adds a "Reset to blank" control to each page row in PagesPanel, behind an
inline confirmation matching the existing delete-confirmation UI. Blanks
the target page's canvas to EMPTY_CANVAS (Task 8) via actions.deserialize.

Since deserialize() acts on the live Frame, resetting a page that isn't
on screen switches to it first (switchPage), then defers the blank via
its own setTimeout(0) -- same-delay setTimeout callbacks fire in
registration order, so switchPage's own deferred load (also setTimeout(0),
registered first) always resolves before the blank does. Verified this
ordering empirically by injecting the reversed-order regression and
confirming the new ordering-sensitive integration test catches it.

Ran the brief's undo-characterization test first: actions.deserialize() IS
recorded in this @craftjs/core version's undo stack, so the confirmation
dialog's "Ctrl+Z undoes this." claim is accurate and was kept.

The reset button lives only in the per-page action row (not the separate
Header/Footer zone-row block), verified structurally and by test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 10:02:59 -07:00
shadowdaoandClaude Opus 5 e5fd74d63d fix(site-builder): repair orphan nodes on initial page load too
useWhpApi's load() called actions.deserialize() directly on the first
page's stored craftState, bypassing repairOrphanNodes -- unlike
PageContext.loadState, which runs it on every subsequent page switch.
An orphaned node (unreachable from ROOT, invisible to Layers/selection)
in a saved project would get silently repaired on the next page switch
but not on the load that actually renders it first. Route the initial
deserialize through the same repair call, with the same console.warn,
so both paths behave identically.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 09:53:36 -07:00
shadowdaoandClaude Opus 5 cf38fdb245 feat(site-builder): array editors scroll to the item picked in Layers
Wires ArrayItemFieldsEditor and FeaturesEditor up to useLayerFocus() so
clicking a virtual row in the Layers tree scrolls the matching item's
card into view in the right-hand array editor. scrollIntoView is
optional-chained on both the queried element and the method itself so
a miss or an environment without it degrades silently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 07:21:27 -07:00
shadowdaoandClaude Opus 5 2438777462 feat(site-builder): Layers shows array-prop items, unplaced nodes, and scrolls
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 07:15:17 -07:00
shadowdaoandClaude Opus 5 f9561c8c54 feat(site-builder): add LayerFocusContext for layers-to-array-editor focus
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 07:08:26 -07:00
shadowdaoandClaude Opus 5 f30fc6efec fix(site-builder): add Hero/CTA components to Layers virtual-rows registry
A grep across every component under src/components/ (not just the 12
files the original registry hardcoded) turns up three more leaf
components with the identical array-prop-as-content pattern: Hero
(HeroSimple.tsx), Call to Action (CallToAction.tsx) and CTA Section
(CTASection.tsx) all render a shared `ctas?: CtaButton[]` prop whose
items are `{ text, href, variant?, target? }`. Without this, a Hero's
CTA buttons still wouldn't appear in the Layers tree.

Verified displayName, prop name and label field against each
component's craft.props defaults and the shared CtaButton type in
sections/_cta-helpers.tsx -- all three matched exactly, no corrections
needed. A follow-up sweep for any further array-prop leaf components
found none: the only other array fields in the tree are nested one
level inside already-covered items (ContactFormField.options,
PricingPlan.features), not top-level component props.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 07:04:54 -07:00
shadowdaoandClaude Opus 5 eb4290a45e feat(site-builder): derive Layers rows from array-prop composites
FeaturesGrid, Tabs, Accordion, PricingTable, Testimonials, Gallery,
ContentSlider, NumberCounter, Menu, SocialLinks, Navbar and ContactForm
store their content in array props rather than Craft child nodes, so
the Layers tree showed nothing underneath them. This adds the pure
deriveVirtualRows() function and VIRTUAL_CHILD_PROPS registry the
Layers panel will consume in a later task.

Verified the registry against each component's actual item interface:
corrected Tabs (label field is `label`, not `title` -- TabItem has no
`title`) and Content Slider (label field is `heading`, not `title` --
Slide has no `title`). Both would have silently fallen back to
"Tab N" / "Slide N" for every existing site.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 06:59:23 -07:00
shadowdaoandClaude Opus 5 38c8d22e5d test(site-builder): cover loadState's orphan-repair wiring via PageProvider
Review found Task 8's prescribed test never mounts PageProvider, so it
never exercises loadState -- deleting the repairOrphanNodes call would
still leave that suite green. Adds a PageProvider-mounted test driving
switchPage into a page with a stored orphaned node, asserting on what
loadState hands to actions.deserialize and that console.warn fires.
Verified load-bearing: temporarily neutering the repair call fails the
new test (ROOT.nodes missing 'stray'), then restored.

Also documents the fallback-branch invariant that `fallback` must
always be a known-safe constant, per review.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 06:53:55 -07:00
shadowdaoandClaude Opus 5 9885b37af5 fix(site-builder): reattach unreachable nodes when loading a page
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 06:48:19 -07:00
shadowdaoandClaude Opus 5 86aacbe1a8 fix(site-builder): repair cyclic orphan clusters in repairOrphanNodes
Review found that a cluster of orphan nodes referencing only each other
(no member's parent points outside the orphan set) made the reattach
loop find zero tops and silently no-op, leaving the cluster unreachable
while reporting repaired: []. Replaced the single-pass reattach with a
loop that re-derives the unreachable set each round and force-reattaches
one representative when no ordinary top exists, guaranteeing
findUnreachableNodeIds is empty after repair. Adds 2-node/3-node cycle
and mixed ordinary-subtree-plus-cycle tests; the original 11 tests are
unchanged and still pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 06:43:46 -07:00
shadowdaoandClaude Opus 5 f0a1508acd feat(site-builder): add pure orphan-node detection and repair
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 06:36:39 -07:00
shadowdaoandClaude Opus 5 66db1db507 feat(site-builder): add insert/colour/format toolbar to the Edit HTML modal
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 06:31:55 -07:00
shadowdaoandClaude Opus 5 b670b436c3 fix(site-builder): stop CodeEditor fallback insert from being lost on CodeMirror mount
insertAtCursor's textarea-fallback branch advanced lastEmittedRef to the
post-insertion value. The mount effect's dynamic import() closes over
`value` as of initial render, so if CodeMirror finishes loading after a
fallback-mode insertion, it mounts with the pre-insertion doc. The only
repair mechanism -- the value-sync effect -- is gated on `value !==
lastEmittedRef.current`, so advancing that ref made the gate see them as
already equal and skip the repair, silently dropping the insertion.

Also clamps caretOffset to [0, text.length] defensively, and adds coverage
for a non-empty-selection replace and for the fallback-to-CodeMirror-mount
transition itself (using the real @codemirror/* packages, no mocks).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 06:26:27 -07:00
shadowdaoandClaude Opus 5 d89930e218 feat(site-builder): expose insertAtCursor/getValue handle on CodeEditor
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 19:24:29 -07:00
shadowdaoandClaude Opus 5 45ee004672 fix(site-builder): discriminate formatHtml whitespace by block/inline boundary, not newline
Round 3 fixed a phantom-space idempotency bug by dropping any boundary
whitespace containing a newline, but that also stripped ordinary
hand-wrapped text like "hello\n<strong>", merging words on a single pass --
directly contradicting this formatter's own "does not reflow text"
contract. The correct discriminator is what the whitespace borders, not
whether it contains a newline: a run between two inline-level things (text,
<strong>, <a>, ...) is always significant and must survive regardless of
newlines, while a run touching a block-tag boundary carries no rendered
meaning and is always dropped. Since this formatter's own emitted
indentation is only ever inserted next to a block tag, that rule also
resolves the original phantom-space bug without any newline special-casing.
tokenize() now peeks each upcoming tag's name once (reused for both the
preceding text run's decision and the tag's own processing) so pushTextToken
can see what's on both sides of a whitespace run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 19:19:32 -07:00
shadowdaoandClaude Opus 5 536e4e9f86 fix(site-builder): make formatHtml close-tag mismatch recovery self-draining
Round 2's tag-name check on close popping fixed misattribution but could
wedge the stack permanently: a mismatch with a real, still-open ancestor
(e.g. an unclosed <p> before a later </div>, a normal optional-end-tag
slip) never drained, so everything after it inherited the stuck depth and
could print out of source order. Close handling now searches the whole
stack for a matching tag, not just the top; frames above a found match are
popped and implicitly closed (no fabricated close tag, just ending their
indentation) before the match itself closes normally. A close with no
match anywhere is still left in place untouched, since it has nothing to
pair with. Also fixes a related idempotency bug in text-run whitespace
collapsing surfaced while verifying this: boundary whitespace containing a
newline (formatter-introduced structural gap) is now dropped entirely
instead of being collapsed to a preserved space like same-line boundary
spaces are.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 19:10:25 -07:00
shadowdaoandClaude Opus 5 51f3fe81b6 fix(site-builder): make formatHtml quote- and raw-text-aware
Code review found two Important bugs from only <pre> being exempted from
the naive </> tag-boundary scan: a > inside a quoted attribute value split
tags and broke idempotency, and <script>/<style> (declared BLOCK_TAGS but
never given raw-text treatment) let JS/CSS < and > desync sibling nesting.
Adds a quote-aware tag-end scanner, generalizes verbatim handling to
<script>/<style>, and makes close-tag stack popping verify the tag name
before popping instead of blindly popping by position.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:59:57 -07:00
shadowdaoandClaude Opus 5 321a193b83 feat(site-builder): add dependency-free formatHtml prettifier
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:48:41 -07:00
shadowdaoandClaude Opus 5 4ac57e1c4c feat(site-builder): HTML blocks get a code-only style panel
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:42:28 -07:00
shadowdaoandClaude Opus 5 0d0d722dd7 refactor(site-builder): extract HtmlCodeField out of GenericPropsEditor
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:38:38 -07:00
shadowdaoandClaude Opus 5 a30e82accf fix(site-builder): HtmlBlock render stops applying style so editor matches published output
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:35:12 -07:00
shadowdaoandClaude Opus 5 6791345f77 docs: implementation plan for the five user-reported site-builder issues
23 TDD tasks across five phases: HTML block (render/export mismatch, code-only
panel, editor toolbar), tree integrity (orphan repair + Unplaced recovery),
reset page/site, Layers virtual rows for array-prop composites, and in-builder
issue reporting (endpoint, table, root-only admin page). Off-canvas drop
prevention is scoped as an investigation whose acceptance criterion is a
written mechanism, not a guessed fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:27:02 -07:00
shadowdaoandClaude Opus 5 fb68fa6485 docs: design spec for five user-reported site-builder issues
Covers off-canvas drops (reachability invariant: prevent, repair on load,
recover via Layers), the HTML block's dead style props (render/export
mismatch plus a code-editor toolbar), page/site reset, Layers virtual rows
for array-prop composites, and in-builder issue reporting backed by a new
whp.site_builder_reports table and a root-only admin page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 10:39:00 -07:00
shadowdaoandClaude Opus 5 c6840db0bb chore(branding): rename "Web Hosting Panel" to "Web Hosting Platform"
Matches the rename in the whp repo now that the Web Hosting Platform
domain is registered. Doc line and a JS comment; no behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 14:17:27 -07:00
jknapp f5d2a23a5f Merge PR #26: surface publish warnings in the editor 2026-07-16 12:34:32 +00:00
shadowdaoandClaude Opus 4.8 c712a69c4a test(topbar): cover publish() warnings wiring into PublishWarnings banner
PublishWarnings.tsx had unit tests for the presentational banner, but
nothing asserted that result.warnings from publish() actually flows
through TopBar's handlePublish into it. That 3-line seam is exactly what
this feature exists to fix -- the backend always returned warnings, and
TopBar discarded them by only checking result.success, so the
contact-form-relay warning was dead code for its entire life.

Adds TopBar.test.tsx asserting: warnings render after a successful
publish with warnings, no banner renders when warnings is absent, a
warning doesn't present as a publish failure, warnings survive the 3s
"Published" flash (fake timers, advanced past 3000ms), and a fresh
publish clears stale warnings from the previous one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 22:12:31 -07:00
shadowdaoandClaude Opus 4.8 d460e8ac33 topbar: surface publish warnings instead of discarding them
handlePublish's JSON response has always included a `warnings` array
(e.g. the contact-form relay's "submissions will not be delivered"
notice), but nothing in the editor ever read it. Adds a PublishWarnings
banner, held in its own state independent of the 3s publishStatus
flash so the customer has time to read it, rendered in both the
desktop and mobile TopBar branches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:52:11 -07:00
jknapp 2b1569202a Merge PR #25: bounce + image crop/resize fix 2026-07-14 23:07:58 +00:00
shadowdaoandClaude Opus 4.8 5c44dd545c fix(site-builder): bounce stays visible + springier; image/video crop fills (cover) + resize shrinks footprint
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:04:50 -07:00
jknapp 2ac62c4e9e Merge PR #24: fix animation delay unit 2026-07-14 19:36:51 +00:00
shadowdaoandClaude Opus 4.8 25dfcbb725 fix(site-builder): coerce bare-number animation delay to a valid CSS time (2 -> 2s)
data-animation-delay is stored as a plain seconds string (e.g. '2'); the reveal
script assigned it raw to el.style.animationDelay, which is invalid CSS and no-ops.
Suffix 's' onto bare numbers (leaving '2s'/'200ms' alone) so entrance-animation
delays actually apply. Backend generateCompiledHTML gets the byte-identical change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 12:34:51 -07:00
jknapp 9bf78fd72d Merge PR #23: fix entrance-animation output 2026-07-14 19:16:59 +00:00
shadowdaoandClaude Opus 4.8 2dcc2b4d21 fix(site-builder): entrance-animation reveal script survives Preview + well-formed void-tag attrs + no-JS fallback
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 12:12:01 -07:00
jknapp 4e0fc78a30 Merge PR #22: enh pages productivity + cross-page clipboard 2026-07-14 14:48:25 +00:00
shadowdaoandClaude Opus 4.8 85dfe181aa fix(pages): duplicatePage must save outgoing active canvas before teardown (avoid dropping live edits)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 07:47:44 -07:00
shadowdaoandClaude Opus 4.8 a698f014b0 feat(site-builder): page duplicate/reorder/set-landing + fix cross-page node copy/paste
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 07:36:00 -07:00
jknapp 204ea5e078 Merge PR #21: enh output/seo/tokens (frontend) 2026-07-14 14:21:10 +00:00
shadowdaoandClaude Opus 4.8 0291ddce9a feat(site-builder): per-page SEO meta + favicon + design-token CSS-var wiring + published-output a11y/perf
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 07:14:56 -07:00
jknapp f7da654c11 Merge PR #20: enh nav 2026-07-14 13:58:11 +00:00
jknapp d6666f6f79 Merge PR #19: enh containers 2026-07-14 13:56:11 +00:00
shadowdaoandClaude Opus 4.8 4a426e3513 fix(containers): only flex-convert Container/Section when vertical-align set (avoid blockifying inline-block children)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 06:55:20 -07:00
jknapp 6e628d68dd Merge PR #18: enh sections 2026-07-14 13:54:52 +00:00
jknapp 8f17b74e2b Merge PR #17: enh forms 2026-07-14 13:49:57 +00:00
shadowdaoandClaude Opus 4.8 54572f648a feat(builder): nav/menu link-to-page picker, page sync, download attr, box-model rollout
NavStylePanel (Navbar/Menu/Logo/Footer):
- LinkPicker: dropdown of the site's pages (read-only via usePages()) plus
  manual URL / #anchor / tel: / mailto: entry, wired into every link-href
  field (standalone Logo href, Navbar logoUrl, Navbar/Menu link items).
- "Sync links with Pages" button in the Links section: repopulates the
  links array from the current pages list (label = page name, href = '/'
  for the landing page else '/{slug}'), preserving any existing CTA link.
  Regression-fix vs the legacy GrapesJS builder, which had this.
- `download` checkbox per link (Navbar/Menu links, standalone Logo href)
  emits the `download` attribute on export for links to files.
- Links/Colors sections now gate on the component actually carrying a
  `links`/color prop, so Footer (no links array) no longer shows a dead
  "Add Link" editor.
- Box-model (Margin/Padding via SpacingControl, Border & Effects via
  BorderControl + box-shadow presets + opacity), AnimationControl, and
  VisibilityControl added for all four owned components, backed by new
  animation/animationDelay/hideOnDesktop/hideOnTablet/hideOnMobile props
  (with blank/default values in each component's .craft.props).

Tests: NavStylePanel.test.tsx (new, 17 tests: LinkPicker modes, sync
preserves CTA, download toggle, box-model/animation/visibility wiring) +
extended Navbar/Menu/Logo/Footer .toHtml.test.ts (download attribute,
craft.props defaults). Full suite: 683/683 passing. `npm run build` green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 06:47:59 -07:00
shadowdaoandClaude Opus 4.8 8bf525c600 feat(builder): surface hidden section/pricing/social props + box-model rollout
SectionTypePanel (Accordion/Tabs/Testimonials/Countdown/NumberCounter/
CTASection/CallToAction/FeaturesGrid), PricingStylePanel, and
SocialStylePanel all gain a Spacing & Border section (margin/padding
per-side, border, box-shadow, opacity), an Animation section, and a
Visibility section, wired to the shared SpacingControl/BorderControl/
AnimationControl/VisibilityControl.

PricingTable's per-card colors (cardBg/textColor/subColor/featColor/
checkColor/btnBg/btnColor) were previously hard-coded literals computed
from featuredBg inside toHtml -- promoted to real optional props (each
falling back to the exact prior literal when unset) and exposed via
ColorPickerField in PricingStylePanel.

SocialStylePanel now exposes SocialLinks' iconShape/gap (already-built
props with no control), plus Icon's bgColor/bgShape/bgSize/link and
StarRating's filledColor/emptyColor, which the panel's generic
iconBgColor/starColor checks never matched since those aren't Icon's or
StarRating's real prop names.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 06:47:58 -07:00