Repository navigation
Conversation
On the design engine, Home is a page of its own at the root, built from blocks on a six-column grid: Welcome, Share an idea, Top ideas, Roadmap, Latest updates, Help search, Help topics, Status, Contact and Links. Each block shows live data in the brand's cards and type, draws nothing when its page is off or it has nothing to show, and streams into the first response. With Home on, the feed moves to /feedback and an old link to the feed at the root follows it there for good. With Home off, the root is the first page the menu shows: the feed as before when that is Feedback, else a redirect. The legacy theme keeps the feed at the root. The menu comes from the design, where Feedback can be hidden and Home can have a tab, and every portal link to the feed takes its path from portalPaths; a test keeps a literal root out of portal code. The sitemap and canonical tags follow the same rule. The cached workspace settings are now kept per build, so the first requests after an upgrade never read a shape an older build wrote.
The cached workspace settings are kept per build, so the tests that seed or forget that copy use CACHE_KEYS.WORKSPACE_SETTINGS rather than the old literal key, which no longer reaches it.
Home's code reached pages that never show it, and the feed's chunk lost its preload at the root. - Share an idea on Home is a light form. What a visitor types goes to /feedback in the navigation's history state, and the feed's composer opens with it once per arrival, so Home loads neither the composer nor the anonymous-session code. - Home takes the page gates from the loader, and its module is linked to the portal layout as the root's own split would be, so what it shares with the layout stays in the layout's chunk. - The feed's split also re-exports the root's component split. The server preloads the chunks holding a route's splits, so the feed is preloaded at the root again, and at /feedback. - The post actions, the sitemap's paths and the feed-search check live outside the modules every page loads. The choice between Home, the feed and a redirect still runs before any route chunk loads, about 1.1 KB in the entry, so the pages that sat within it of their script ceilings take their new readings.
Home gets its own section in Sections, and the preview shows it. - Turning Home on starts it with a layout built from the pages the workspace has: Welcome, Share an idea with Status, Top ideas with Latest updates, the roadmap and help topics. - Home's headline, then its blocks in order. Each block takes a width it allows, moves up or down, and goes; Top ideas takes an order and a count, Latest updates a count, Roadmap which roadmap, Links a heading and its links, and Welcome its text. A block whose page is off says so. A link reaches the draft once it has a name and a whole web address. - With Home on, the preview switches between Home and the feed at /feedback, and changing Home shows Home. Picking a block in the preview opens its row. While Home is on, the band's own headline is named the Feedback headline. - The preview carries the draft's Home and menu, and the root shows Home to a preview that asks for it with page=home, whatever the saved design leads with, with every block's strings. - Review and publish names Home's changes and counts its blocks; an empty headline reads None. The portal path hooks move to components/public, where their callers live, since they read the preview's draft menu.
Its icons were modules it shared with the post page, the inbox and other admin pages, which split them into chunks of their own there: one or two more requests on each.
On the design engine the header's menu comes from the design, but the Navigation tab still edited the legacy menu, so its changes never showed. The menu now lives in Sections, under the header, and writes the draft. - While Home is on: Home in the menu, and its tab's name. - The pages and links in order, each shown or hidden and renamed, with links added and removed, in the same editor as before. Feedback can be hidden too, as the design engine allows. - The rows reach the draft once every link in them is whole and, with Home off, they show a page to start on; until then a note says what is missing. A menu changed elsewhere, such as by a discard, replaces them. - Review and publish names the menu and Home's tab name, and counts what the menu shows. The Portal settings page on the design engine is Appearance alone; its Navigation tab is gone.
Appearance now imports the portal header's menu module for the menu editor, which moves a chunk boundary the post page loads across: 0.2 KB more script there, just over a ceiling it sat at.
Setup publishes the design a new workspace starts with: its branding converted, as an established workspace's is on read, with Home on and laid out from the pages its goals turned on. It is the first version, from setup, with no author. Only setup does this, and only for a new workspace with the design engine on and no custom CSS a switch would have to carry over. A workspace that already has a design keeps it, and an established workspace's root never changes on upgrade. The setup tests that stand in for the settings helpers now pass the real ones through, since setup reads the portal config to lay Home out.
A workspace chooses what visitors call what they post (Idea, Post, Feature request or Suggestion) and what groups them (Board or Category), and the portal, the widget and the unsubscribe page say it in every language. - Brand > Words in Appearance: a tile per choice with a sentence the portal would say. The preview shows it before publishing, Home's block names follow it, and the menu labels and English headlines that still name the old word are offered for rewording in the same draft. - 112 visitor strings select on postWord and boardWord, with idea and board as each select's other branch; 30 that use the words another way stay. PortalWordsIntl passes the words into every message, so no call site changes: the portal, unsubscribe and widget set the published words, the preview the draft's, and the admin and onboarding keep post and board. Each nested provider applies them again. - All 12 other catalogues word every choice, each language with one noun per choice anchored on its translator's terms. - A guard fails an English visitor string that names a post or board without the select, and any catalogue whose choices fail to format, read alike, or miss the default. The placeholder parity check now parses messages, since a select branch's first word is not a placeholder. - The Boards settings say what visitors see where it differs from the admin's words. The English of each converted string, every choice included, lives in the code, so the post page's script ceilings take their new readings.
A notification row is stored in the admin's words, a post and a board, and the bell words it again in the reader's: the portal's own word there. The parity check now formats as the admin does.
The portal now says idea by default: the composer's field is Idea title, the empty filter state says No ideas match your filters, and a new post's toast says Idea submitted.
The status change, new comment, mention and linked-feedback emails said "your feedback" and "View Feedback". They now say the portal's word: "Your suggestion is now Planned!", "New comment on your idea", "View feature request", and the status subject too. Idea when none is chosen. The hook context reads the word as the portal does, from the workspace settings' design, once per event, and the post subscribers' and mention targets carry it to the senders. An email still goes out, saying idea, if the design cannot be read.
A workspace can write three strings in its own words, per language: the composer's prompt, its submit button and the empty feed's title, under Brand > Words. The portal's composer, the empty feed and Home's Share an idea use them for readers of that language. A language left blank says the chosen word's translation, never another language's wording. The feed's and Home's headlines get a language picker, so each language is written on its own. The portal layout provides the saved wording through FeedCopyContext, in a module the feed and Home already share, and the preview provides a draft's in its place. The widget embed and the admin feedback page, which sat at their script ceilings, take their new readings.
Main already reads 473.5 KB on the admin feedback page against its 466.1 KB ceiling, inside the 2% tolerance. The stack's 2 KB took five admin pages past it.
A workspace's CSS can style the portal by part name rather than by class names that change with the code: [data-qb-part="post-card"]. - Twelve parts are marked: header, logo, nav-item, band, headline, home-block, composer, post-card, post-title, vote-button, status-badge and filter-panel. States are data-state: active for the current menu item, open for a composer being written in, voted for a voted vote box, done for a post whose status is complete or closed. - lib/shared/design/parts.ts lists each part's states, the tokens that style it and the files that draw it, with aliases for renamed parts and rewritePartAliases, which keeps old names working. - A contract test keeps every listed part marked where it is drawn, with its states, fails on a part the manifest does not list, and checks each documented token is one the design declares. Every card now carries its part names, so the portal home's HTML ceilings take their new readings.
A workspace can write its own CSS over the design in Sections, styling the portal's parts by name. It renders in the tenant layer, the preview shows it, and review counts it in lines. The check on save refuses @import, url() anywhere but the workspace's own storage, expression(), behavior and <, as a refusal with the line. It warns on internal class names, offering the part to use, which Fix it for me swaps in, and on !important, tokens no part reads, unknown or renamed parts, and text and background colours on a part below 4.5:1. Old part names are rewritten as the design is read. It parses with css-tree's self-contained build, pure JavaScript with its data inlined, so the server bundle needs neither a native module nor data files beside it. Custom CSS stays a plan feature: new or changed CSS asks the plan, while CSS already published stays and removing it is always allowed.
A portal with its own CSS stays on its old theme until someone switches. Its old Settings › Portal page now offers the switch: today's portal beside the portal switched, a report on the CSS, and Switch, held while the CSS has refusals. History offers the way back. The switch publishes today's branding converted, with the theme variables the CSS sets on :root and .dark read in first, since the design sets those variables itself and a tenant-layer :root would stop applying. The CSS carries over unchanged without asking the plan, as it is the portal's already. Switching back unpublishes the design, so the old theme, never touched, shows again; the design's versions stay. Both are audited. The switched frame is drawn by the engine on the server: with ?design=switch the bootstrap draws the portal switched, for a dashboard session that can edit branding only. The CSS check now offers the post-card part for .post-card.
A family loads with a dynamic import of its CSS module, so the client build gives its files hashed names the server could not know, and a design's fonts arrived only once the page's script asked: text drew in a fallback face and swapped after hydration. A build plugin now records each font module's stylesheet and Latin woff2 files from the client build, by the font id the loader gives it, and hands them to the server build as a virtual module. The design view carries the links: each family's stylesheet and at most two preloads, the body at 400 and the headings at their weight or the nearest the family has. Inter ships with the app's own stylesheet and needs neither. Three admin pages' script ceilings take their readings: main already reads about 2% over them, and these bytes leave them under 0.1 KB from the limit.
Importing the type from the build plugin gave server code a dependency on lib/build, which the dependency graph does not allow for.
Brand › Fonts gains Your own font: up to six woff2 files with the weight and style read from each name, a family name, headings, body or both, and a licence to tick before anything uploads. The draft's font becomes the uploaded family, with the font the slot had as its fallback; both pickers list it, and review names the change by the family. The upload URL signs font/woff2 under fonts/ for the branding permission, refusing other names, files over 400 KB, no storage and a plan without custom CSS as values. The upload itself checks the wOF2 signature, the prefix and the size. fonts/ is public and always proxied from the portal's own origin, since @font-face fetches in CORS mode. The schema takes an uploaded key only as a woff2 under fonts/ in characters that cannot end a url(). The theme's CSS carries an @font-face rule per file, and the head preloads the file nearest the body's and headings' weights.
The app's heading and tracking rules were unlayered, so they beat every layer, the workspace's tenant CSS included: a portal's own CSS could not change its headline's weight or tracking. On a portal drawn by the design engine they now sit in a design layer between utilities and tenant. They still beat utilities, as before, and the workspace's CSS beats them. The admin and portals on the old theme keep the unlayered rules, scoped with :where so their specificity is what it was.
This was referenced Nov 1, 2026
This was referenced Nov 1, 2026
Member
Author
|
Superseded by the re-cut builder stack on current main: #917 (engine) → #918 (server) → #919 (portal) → #920 (builder) → #921 (AI) → #922 (tooling), with the critique's wave 1 in #924. The new stack merges main once, renumbers the migrations to 0310+, and has CI green with all review threads addressed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Home becomes a page of its own, built from blocks, and the portal speaks the workspace's own words. The portal's parts get stable names, so a workspace's own CSS can restyle them safely.
In this PR
Replaces
#846, #847, #848, #849, #850, #851, #852, #853, #854, #855, #856, #857, #858
Second of five stacked PRs.