Skip to content

feat(builder): wave 1, make the brand stick and easy to find - #924

Draft
mortondev wants to merge 18 commits into
feat/builder-6-toolingfrom
feat/builder-7-wave1
Draft

mortondev wants to merge 18 commits into
feat/builder-6-toolingfrom
feat/builder-7-wave1

Conversation

@mortondev

Copy link
Copy Markdown
Member

Wave 1 of the portal builder critique: make the brand stick and make it easy to find. Stacked on #922.

What changes

  • Rolling back a build keeps the brand (F22). A design field the build doesn't know is now dropped on its own. Before, one unknown value blanked the whole design. A test covers every optional choice field in the design.
  • Looks keep your brand (F27). A Look changes only the treatment: shape, layout, hero treatment, type scale and case. Your colours, fonts, logo and light/dark mode stay. Tiles preview your own brand, and the hero text on them is contrast-checked. "Use its colours too" applies the Look's palette as one undo step.
  • Match your brand is easy to find (F04). It's the first row of Theme > Brand kit: a website field filled from your domain, plus up to 3 screenshots. It's offered until a match has run, tracked by the new brand.matchedAt. Before, it was hidden as soon as a logo existed, and onboarding always adds a logo.
  • No AI key? Use from your website (F05). The row reads the site without a model and offers logo, favicon, share image and one brand colour as checkboxes, applied as one undo step.
  • First open points at brand setup (F26). The coach mark sits on the Match row. The menu hint shows only over the menu, closes on Escape, and reads "Pick an item to rename or move it."
  • Logo colours (F72). Uploading a logo fills in its colours, and "From your logo" swatches appear under Brand colour.
  • Onboarding hands off to the builder (F18). The step is now "Make the portal look like your brand" and opens /design?match=<domain>&from=onboarding, which runs the brand match into the draft. It counts as done once a match has run, or once the Brand kit has an icon, colour and font.
    • Accepted website branding now also writes into the design draft on the design engine. Before, it went only to the legacy theme, which the published design ignores.
    • Settings > General labels the workspace logo "Admin and emails".
  • Fonts (F06). The match reply now names the real place (Theme > Brand kit > Fonts). When the site names a font we don't bundle, it offers "Upload ", opening the upload form already filled in.
  • Posts, not ideas (F03). The AI's examples and prompt use posts and feedback, and English hero, subtitle and button words are reworded if they still say "idea".

Notes

  • Theme opens once per page load on a workspace that hasn't matched yet, so the coach mark can point at the Match row.
  • "Use from your website" counts as a match, so the AI stops offering it. Match again is always there.
  • Not done: undoing accepted website branding doesn't take the logo and colour back out of the draft. The draft isn't live and can be discarded.
  • Follow-up: "Add " for open-licence fonts we don't bundle.

Testing

  • Typecheck and lint are clean.
  • About 9,200 tests across the designer, design domain, onboarding, settings, locales, policy and portal components pass. GRAPH.md and MATRIX.md were regenerated for the new imports and server function.
  • Checked in a browser on a fresh seeded database:
    • Theme opens on first visit with Match your brand first, the website filled in and the coach mark showing.
    • The Look tiles draw in the workspace's colours and show "Use its colours too" on hover.
    • The launch plan step links to /design?match=example.com&from=onboarding.
    • No page errors.

The builder will offer the match until one has run, rather than until the
design holds a logo or colour (onboarding's logo alone is not a matched
brand). Stamped by the brand match on the design it returns.
… build does not know

After a rollback, a stored design can carry an option a newer build added to an
optional field (bandGlow, bandShape, shape.hover and others). The default design
has no value at those paths, so repair gave up, readStoredDesign returned null,
and the portal lost its whole brand, published and draft alike.

An optional field that fails validation (unknown value or wrong kind) is now
dropped on its own and recorded in `dropped`; the rest of the design is kept.
Required fields still fall back to the default design's value as before.
The test is generated from the schema: every optional enum field gets an
unknown value and must read with only that key dropped.
The portal is a feedback board: visitors write posts. The design AI's few-shot
examples and prompt text taught it to say "ideas", so generated hero headlines,
subtitles and button labels did too.

The examples and prompt now use posts and feedback, and the prompt says so
outright. A deterministic step (words-posts.ts) also rewrites English "idea" and
"ideas" in the words the AI returns (hero and feedback headline, subtitle,
prompt, submit button) to post and posts, with "an idea" becoming "a post".
Other languages are untouched. A golden test covers it, plus a guard that no
example teaches "idea". Designer preview text and one block description no longer
say ideas. The English catalogues were already clean and the default composer
button is already "Write a post".
One click on a preset look replaced brand.light, brand.dark and the fonts,
which undid a brand match, and the tiles and hover previews drew each look
in its own palette rather than the workspace's.

A look now gives only its treatment (corners, layout, hero, type scale,
case and exact values). withLook keeps the draft's palettes, tuned colours,
faces, heading weight and mode by default, reusing keepOwn, which moves from
presets.ts to looks.ts so both paths share it. Passing itsColours takes the
look's colours, fonts and mode as well; applyPreset keeps its old whole-look
behaviour through it.

Tiles draw the brand with the look's treatment, from the resolved design so
the contrast solver picks the hero letters, with the workspace logo in the
band. Each tile offers "Use its colours too" on hover or focus, applied as
one step labelled "Look and colours: <name>"; a plain choice is labelled
"Look: <name>".
…atch

The launch plan's branding step stopped at a logo, linked to Settings >
General and was done once a workspace logo existed. On a portal drawn by
the design engine that logo is not the portal's brand, and website
branding accepted from onboarding wrote only the legacy branding
settings, which a published design ignores, so it silently changed
nothing visitors saw.

- The step is now "Make the portal look like your brand". In the builder
  it links to /design?match=<domain>&from=onboarding (domain when the
  logo lookup knows it), and on the legacy theme it keeps the logo page.
- The builder handles ?match on arrival (useMatchOnArrival): it runs Ask
  AI's own brand match into the draft, with its single Undo, then clears
  the address so a reload does not rerun it. Without AI, or without a
  website, it opens Theme on the Brand kit. /design already requires
  SETTINGS_BRANDING.
- The step is done when a brand match has run (brand.matchedAt on the
  published design or the draft) or the Brand kit has an icon, a brand
  colour and a font that differ from the defaults; a logo alone no
  longer counts. Legacy portals keep the workspace logo rule.
- Accepting website branding also writes the logo and colour into the
  design draft once a design is published, keeping an icon or colour the
  design already has.
- Settings > General labels the workspace logo "Admin and emails" once a
  published design carries the portal's own brand.
…n the Brand kit

"Match my brand" hid itself as soon as the design held any logo or colour,
and onboarding maps the workspace logo into logo.mark, so onboarded
workspaces never saw the fastest way to brand the portal. The offer now
depends on whether a brand match has ever run (brand.matchedAt).

The stamp moves from brandMatch into runBrandMatchRequest, so every path
(screenshots, a capture, the site's code alone) records it on the draft it
saves and on the design the builder adopts.

Theme > Brand kit gains a first row, Match your brand: a website field
prefilled from the workspace's known domain and a drop zone for up to three
screenshots. It runs the same match as Ask AI through a new reusable entry
point, useDesignAi().startBrandMatch({ website, screenshots, from }), so the
reply, the check and its single Undo show in the conversation. Once a match
has run the row stays as a quiet "Match again".
Without an AI key there was no way to bring the brand in from the brand's
own site, though the site reader and its extractors need no model. When AI
is off, the Brand kit's Match row becomes "Use from your website": it reads
the site from its code, stores what it gives, and offers checkable chips
for the logo, favicon, share image and one brand colour. The checked ones
go into the draft as one undo step that also records brand.matchedAt, so
the row turns quiet ("Use again") like a match. Font names are left out:
a site's code names them too unreliably to offer.

The read is a new server function, readSiteBrandFn, gated on
SETTINGS_BRANDING, returning refusals (not a website, unreadable, nothing
found) as values. The authz matrix golden is regenerated for it.
…enu hint

The first-open coach mark explained the menu, sat over the picked part,
ignored Escape and never mentioned the brand. While no brand match has
run, the first open now shows Theme with a coach mark under the Match
your brand row: "Paste your website to match your brand". It sits in the
panel's own order so the keyboard reaches it, and closes on Got it,
Escape, or once a match or site read starts.

The menu hint shows only while the menu is hovered or the header picked,
sits under the whole header so it never covers the picked part, closes on
Escape (from the builder or the canvas), and says one line: "Pick an item
to rename or move it." Coach marks share one remembered-per-person hook.
A logo upload never suggested a brand colour: logo colours were filled
only by hand, from a row at the end of Colours labelled for status
markers. Uploading an icon or a full logo now reads its colours with its
ink and stores them as the logo colours in the same undo step, and the
Brand colour row shows them as "From your logo" swatches. Picking one sets
the brand colour as one named step ("Brand colour from your logo"); the
colour already in use is not offered again.
…callback

useMatchOnArrival now only decides what an arrival link asks for and
clears the address; the match itself comes in as start(website), so the
builder's shared brand match entry point can be passed in at merge time
without touching the hook. The shell passes Ask AI's site match for now.
Fonts are the most common miss in a brand match, and the reply told
people to "upload your own fonts in Brand", a panel that does not exist:
the upload lives in Theme > Brand kit > Fonts.

- The note now names the real place: Theme > Brand kit > Fonts.
- When the brand's site names a face no bundled face is (drawn in the
  nearest instead), the match result carries it (font: family and slot,
  both slots when the site sets one face throughout), unless the design
  already has that family uploaded.
- In the builder the reply card gets "Upload <family>", which opens
  Theme with the Brand kit's font upload open and its name and slot
  filled in, and a plain "Fonts in Theme" link to the Brand kit's fonts.
  The reply itself stays one line with its one Undo.
Onboarding's arrival match runs through the Brand kit's startBrandMatch,
so there is one match path; GRAPH.md regenerated for the new imports.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant