site-builder: feedback batch (social, features, header menu, spacer)

Five of six items from user feedback (Contact Form email delivery split
into a focused, live-tested follow-up):

- Social Links: add Spotify + Twitch (FA 4.7.0 already ships both glyphs).
- Features Grid: per-feature icon/image toggle (upload + URL) and an
  optional button (text + url); render, settings, and HTML export updated,
  backward compatible with existing icon-only features.
- Header: seed the default header with a Navbar (logo + Home/About/Services/
  Contact) so new sites open with an editable menu-with-links instead of an
  empty header zone. Adds a vitest guard that the seed deserializes and
  exports a real <nav>.
- Canvas: slim the empty header/footer placeholder from a padded band to a
  thin hint line so an empty zone no longer reads as a stray spacer.

Design spec: docs/superpowers/specs/2026-07-06-site-builder-feedback-batch-design.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-06 17:53:22 -07:00
parent d0925d9e2d
commit 53c40f856f
6 changed files with 268 additions and 6 deletions
+55 -1
View File
@@ -60,6 +60,60 @@ const EMPTY_HEADER =
const EMPTY_FOOTER =
'{"ROOT":{"type":{"resolvedName":"Container"},"isCanvas":true,"props":{"style":{"minHeight":"60px","backgroundColor":"#0f172a","color":"#94a3b8","padding":"40px 24px","textAlign":"center"},"tag":"footer"},"displayName":"Container","custom":{},"hidden":false,"nodes":[],"linkedNodes":{}}}';
// Default header seed: a ROOT header Container holding a Navbar with default
// links. New sites previously opened with an EMPTY header (just a bare
// Container), so there was no menu to edit and the empty zone rendered as a
// stray band above the page. Seeding a real Navbar gives every new site an
// editable menu-with-links out of the box (and removes the empty-header gap).
// Node shape matches serializeTreeForCraft() / Craft's actions.deserialize().
export const DEFAULT_HEADER_STATE = JSON.stringify({
ROOT: {
type: { resolvedName: 'Container' },
isCanvas: true,
props: { style: { width: '100%' }, tag: 'header' },
displayName: 'Container',
custom: {},
hidden: false,
nodes: ['header-navbar'],
linkedNodes: {},
},
'header-navbar': {
type: { resolvedName: 'Navbar' },
isCanvas: false,
props: {
logoType: 'text',
logoText: 'MySite',
logoImage: '',
logoWidth: '120px',
logoUrl: '/',
logoFontFamily: 'Inter, sans-serif',
logoFontSize: '20px',
links: [
{ text: 'Home', href: '/' },
{ text: 'About', href: '#about' },
{ text: 'Services', href: '#services' },
{ text: 'Contact', href: '#contact', isCta: true },
],
backgroundColor: '#ffffff',
textColor: '#3f3f46',
hoverColor: '#3b82f6',
ctaColor: '#3b82f6',
ctaTextColor: '#ffffff',
padding: '16px 24px',
navAlignment: 'space-between',
isSticky: false,
showMobileMenu: false,
style: { borderBottom: '1px solid #e4e4e7' },
},
displayName: 'Navbar',
parent: 'ROOT',
custom: {},
hidden: false,
nodes: [],
linkedNodes: {},
},
});
const PageContext = createContext<PageContextValue>({
pages: [],
headerPage: { id: HEADER_ID, name: 'Header', slug: '__header__', craftState: null, headCode: '' },
@@ -106,7 +160,7 @@ const DEFAULT_HEADER: PageData = {
id: HEADER_ID,
name: 'Header',
slug: '__header__',
craftState: null,
craftState: DEFAULT_HEADER_STATE,
headCode: '',
};