CreatorTrack UI Kit¶
URL: https://mkdocs.justinsforge.com/memory/general/reference_suite_ui_kit/
CANON UPDATE (2026-06-18) , renamed to CreatorTrack. This is the CreatorTrack shared UI kit (product formerly "The Suite"/"Workspace"), built and previewed at
dev.creatortrack.ai. It is step 1 of the build order: reusable UI components first (one shared kit, revise a token to retheme all), then the Notion-class page framework as the foundation, then apps that plug into that framework. The data layer formerly "lifeos" is now the core database (core). Full canon (with tech-stack overview): creator-suite-vision-strategy.
The shared design system for forge-suite (the Next/React Suite: finance + future Clips/Fitness/Workspace modules). Created 2026-06-17. Formalizes the workspace.justinsforge.com Notion look so every module is consistent and we revise once.
What it is¶
- Tokens:
app/theme.css, the:rootCSS variables (palette, font, radii, spacing), sourced from the workspace design ([[reference_workspace_palette]] + [[reference_workspace_font]]; the Flask source isforge_workspace_ui_base.py:root). Imported inapp/layout.tsx, so it's global to ALL routes. This is the single retheme knob , edit a token, every module re-themes. Usevar(--token), never hardcode a hex. - Primitives:
components/ui/index.tsx(+components/ui/icons.tsx). Built only on tokens. Import from@/components/ui; never hand-roll per-module styles. - STUDIO:
/studio(renamed from/kit2026-06-18;/kitnow 307-redirects in viaapp/kit/page.tsx). Studio is the internal design+build tool ("the tool to build the tool"), merging the 3 pro surfaces: component explorer + token/theme editor + (toward) a visual page builder. Shellapp/studio/page.tsx= topbar tabs, floating Theme dock mounted once across all tabs. Tabs: Components (components/studio/ComponentsGallery.tsx, the old /kit catalog + playgrounds), Preview (components/studio/PreviewTab.tsx, renders any real route in a phone 390 / tablet 834 / desktop device - Token store (
components/kit/token-store.tsx,TokenStudioProvider/useTokenStudio, 2026-06-18): shared state; Studio topbar drives Save / Undo / Redo / last-updated. STAGED + IMPERATIVE model (revised 2026-06-18): dragging must NOT re-render the tree (that flashed like a page refresh). Two contexts:useTokenStudio()= STABLE api (getValue/setValue/registerScope/save/undo/redo);useTokenStudioState()= volatile (dirtyCount/canUndo/lastSaved) consumed ONLY by the topbar.setValueapplies edits IMPERATIVELY (CSS vars) onto registered preview scopes ,Showcaseregisters its preview pane,PreviewTabregisters the iframe's documentElement (same-origin) , so no React re-render per drag frame and Studio chrome never changes. Each control keeps LOCAL state. Edits sync over a BroadcastChannel (creatortrack-studio-tokens) so a 2nd tab/screen (Preview) follows live. Save writes theme.css → dev HMR → global. Undo/redo = debounced history. The Theme sidebar (TokenPanel.tsx, app variant) toggles via a gear icon in the Studio topbar (between the title and the Components tab). - Level 1 token playground (
components/kit/TokenPanel.tsx+ manifestlib/kit-tokens.ts): a slider/color-picker per token; editing callsdocument.documentElement.style.setPropertyso the WHOLE app re-themes live (no rebuild, no LLM round-trip). Save (in the topbar) POSTs dirty tokens toapp/api/kit/tokens/route.tswhich rewrites only the matched--name: value;lines IN PLACE inapp/theme.css(inline comments preserved). Dev-only (403 in prod), allowlist + value sanitization (rejects braces/semicolons/comment marks, i.e. CSS injection). Reset drops live inline overrides back to the saved file. Add a tunable token = one entry in the manifest. - Level 2 per-component controls (
components/kit/Playground.tsx): schema-driven controls beside a live preview; toggle a component's props with no code edit. Control types: enum/boolean/text/range (slider+number for size/margin/padding/radius). Wire a component by adding aControl[]+ arender(props). Showcaselayout (components/studio/Showcase.tsx, 2026-06-18): the reusable "Preview + Controls" frame , two SEPARATE rounded panels with a gap: 2/3 live preview (left) + 1/3 controls (right). Controls panel has a tab bar (Controls tab;controlTabsprop for more later).Playgroundrenders through it; each component's controls now include size/padding/radius/width sliders (range control type), applied via inline style. Components shown: Button/Pill/Tag/Badge/Avatar/Input/Checkbox/Switch/ NavItem/Sidebar; reference galleries (palette/menus/icons/scrollbar) full-width below.- NavItem
sizeprop (md|lg, 2026-06-18): lg = large inline-menu row (42px, 15px, icon 19). The app sidebar variant = a floating MENU-style panel (.ct-sidebar--app:--cardbg, border,--r-lgrounding,--elev-2shadow, 12px inset from edges) paired with lg NavItems; main stays the compact Notion page-tree. - GOTCHA fixed 2026-06-18:
.ct-sidebar*(+.ct-scroll) MUST live in globalapp/theme.css(imported by the root layout), NOT inapp/(suite)/workspace.css. workspace.css only loads inside the(suite)route group, so/studio(outside it) got NO sidebar styles , the Theme settings app-sidebar rendered unstyled. Moved to theme.css; verified.ct-sidebar--appnow ships in the /studio route CSS. - Token-bound controls (2026-06-18): a Playground
Controlmay settoken: "--name". Bound controls read/write the token store (not local state), so dragging them counts as a tracked change (unsaved count, undo/redo, Save to theme.css) and rethemes the whole app; a blue dot marks them. Local controls (e.g. a Button's label, a demo box height) only affect that one preview and are NOT tracked , this was the "drag shows no changes" confusion. Scrollbar (color/hover/width/radius/speed) and Menu (width/pad/item-font) are now token-bound; Icon set (size/stroke/color) stays local. - Verified 2026-06-18: tsc clean;
/kit200 on :3060; save round-trip 200 + comment survives; non-allowlisted token and injection value both 400. Level 3 (on-canvas drag-handles) deliberately deferred.
Component inventory (built 2026-06-17, Worker A; every value ported from Flask source or measured)¶
| Component | What it does | Backing value source |
|---|---|---|
Button |
variants default/accent/ghost/danger, sizes sm/md, icon, loading, disabled |
Flask .uidlg-b (default/primary/danger), padding 7px 14px / 13.5px |
Card / CardHead |
standard panel + header row | --card/--line/--r-lg |
Pill |
outline chip (default/accent/pos/neg/warn) | tokens |
Tag |
filled select-property chip | --accent-soft/--sel |
Badge |
count indicator | tokens |
SectionLabel |
sidebar divider | measured Notion 12px/500 mixed-case --mut3 |
NavItem |
sidebar page row, active+hover | measured Notion 30px tall / 14px-500 / --mut / radius6 / hover --sel |
Group |
collapsible sidebar section, rotating chevron | Flask .pfold |
Avatar |
initial mark, grad = workspace switcher |
Flask .wsmark 28px/radius9, --grad |
Input / Textarea |
label/placeholder/hint/error, focus ring | Flask .uidlg-in bg --bg/radius8/focus --accent2 |
Select |
menu-backed select | composed on Menu + field |
Menu |
floating menu: items/labels/separators/icons/danger/kbd, keyboard nav (up/down/enter), click-outside | Flask #nodemenu w240/radius10/pad6, .nm-act 13.5px/pad 6-9 |
DropdownMenu |
trigger + anchored Menu | " |
ContextMenu |
right-click Menu at cursor | " |
Popover |
anchored floating panel (filters/property editors) | --elev-2 |
Tooltip |
hover label | Flask .bktip radius6/pad 4-9/13px, --elev-1 |
Modal |
overlay+panel, header/body/footer, ESC + click-outside + focus trap | Flask .uidlg-ov/.uidlg scrim .55/380px/radius13/--elev-5 |
Tabs |
underline-active view tabs | Flask database views |
Checkbox/Radio/Switch |
selection controls, accent fill | Notion 16px box / accent2 |
Spinner / Skeleton |
loading states | shimmer on --sel/--hover |
ToastProvider/useToast |
lightweight notifications | --elev-2 |
Icon (icons.tsx) |
~50 named Lucide glyphs, one source for all modules | Flask LUCIDE map verbatim, 24x24 stroke-2 |
Scrollbar (2026-06-18) |
shared custom scroll surface; wraps any overflow region (or apply .ct-scroll); axis y/x/both |
.ct-scroll in theme.css, --slider-* tokens; thumb idle #4d4d4d, hover/active #c0c0c1 (Justin spec); tokens: --slider/--slider-h/--slider-w/--slider-radius/--slider-pad/--slider-speed/--slider-track; full control set in the /kit Scrollbar workbench section |
Sidebar (2026-06-18, components/workspace/Sidebar.tsx) |
shared sidebar shell, 2 variants. main: attached 3 sides (flush, full height), inner-edge border, pull-in/out via collapsed. app: floats inside content, padded/rounded/bordered (module sub-nav). Both: fixed head + scrollable body (.ct-scroll) + fixed foot. Props: variant/collapsed/width/header/footer |
.ct-sidebar* in workspace.css; WorkspaceShell uses variant="main"; demo (main+app) in Studio Components |
AccountMenu (2026-06-18, components/workspace/AccountMenu.tsx) |
account row pinned at sidebar BOTTOM; click opens upward popup (email header, Settings/help/billing/apps/log out) via kit Menu; click-outside closes |
.ws-acct*; takes email/name/plan; shell passes userEmail from currentUser() |
HintTooltip (2026-06-18) |
small floating tooltip of action/shortcut rows (bold label + muted hint), e.g. the edge-resize hint | --card/--elev-2; rows={[{label,hint}]} |
ResizeHandle (2026-06-18) |
draggable edge strip for sidebars/panels: hover lightens inner edge (.ct-resize:hover:after → --mut3) + col-resize cursor + HintTooltip near cursor; drag resizes (clamped), click closes (onClose), optional Ctrl+ (shortcut) |
.ct-resize in global theme.css; getWidth/setWidth/min/max/onClose/closeHint/shortcut. Wired into Sidebar (resizable prop) + TokenPanel; main sidebar resizable (Ctrl+ collapses), Theme settings app sidebar resizable (edge-click hides) |
Shell scroll fix (2026-06-18): .ws-root is height:100vh; overflow:hidden (was min-height), so ONLY the sidebar body + .ws-main scroll internally, never the whole page.
Tokens added to app/theme.css (2026-06-17)¶
--danger/--danger-bg/--danger-ink, --focus/--ring, --accent-soft/--accent-ink,
elevation scale --elev-1..5 (tooltip→modal, from Flask box-shadows), menu metrics
--menu-w/--menu-pad/--menu-item-fs/--menu-item-pad/--menu-kb, nav-row metrics
--nav-row-h/--nav-row-fs, --scrim. All sourced from Flask measured values (documented
in a comment block in theme.css). Verified: /kit compiles + serves 200; finances :8096 untouched.
Phase 1 token freeze (2026-06-18, [Phase1 Worker])¶
Foundation locked ahead of the parallel component build. Added the last measured page-frame
tokens so Phase 2 (menus, popovers/modals, scrollbars, pages/layout) needs zero hardcoded
values: --fs-h1/--fw-h1/--lh-h1 (measured Notion page title 32px/700/lh 1.2),
--topbar-h: 44px (measured Notion topbar), --modal-w: 380px (Flask .uidlg). All five
added to lib/kit-tokens.ts (new "Page frame" group) so they are allowlisted + tunable in
Studio. Also restored --slider-h/--slider-w to spec (#c0c0c1/10px) after a live
Save-test had left #1919cc/15px in the file. theme.css is now declared frozen: it is
the single retheme knob; Phase 2 workers read var(--token) and never edit it (the
integrator adds any new token in one serialized step). Authoring contract: forge-suite/COMPONENT_AUTHORING.md
(one folder per component, shared-file rule, registration via a studioEntry exported from
the component folder, typed by components/studio/types.ts StudioEntry; integrator does
the final gallery wiring). Harness reverified 2026-06-18: tsc clean (one ignorable
.next/types/validator.ts route-group artifact), /studio 200, /kit 307, token Save
round-trip 200 + comment preserved + non-allowlisted/injection both 400.
Token-bound control bugfix (2026-06-18, [Phase1 Worker]): the blue-dot (token-bound)
controls in Studio were not updating the preview. Two causes: (1) the Menu controls
(--menu-w/--menu-pad/--menu-item-fs) were NOT in lib/kit-tokens.ts, so the store
neither applied them to the preview scope (it only iterates manifest tokens) nor allowed
saving them (Save returned 400). Fix: added a "Menu" group to the manifest (also surfaces
--menu-item-pad/--menu-kb). (2) The Scrollbar controls (--slider-*) did
nothing because .ct-scroll in app/theme.css set the standard scrollbar-width: thin +
scrollbar-color. Chrome 149 now SUPPORTS those standard properties, and when they are set
it IGNORES the ::-webkit-scrollbar rules entirely, so --slider-w (px width), hover color,
and radius never applied (scrollbar-width:thin is a fixed thin bar). Verified live in
Justin's Chrome 149/Win: a webkit-only test box rendered a 24px bar, a std+webkit box rendered
9px (thin). Fix: gate the two Firefox standard properties behind
@supports not selector(::-webkit-scrollbar) (true only in Firefox; Chrome reports the
selector supported, so it skips the block and uses the token-driven webkit scrollbar). Also
kept a one-reflow repaint nudge in applyToScope (components/kit/token-store.tsx) so live
drags repaint the webkit scrollbar immediately. Verified end-to-end in-browser: dragging Width
20->6 moves the rendered scrollbar 22px->7px live, Save enables. RED HERRING that cost time:
Justin's browser window was very small, squishing the preview box so even correct changes were
invisible; confirm a normal window size when a preview "does nothing". Verified: tsc clean;
--menu-w save now 200 (was 400).
Section 0 foundation primitives (2026-06-18, [Phase1 Worker])¶
Built the shared behavioral layer ALL components inherit, in components/primitives/ (barrel
@/components/primitives): useAnchoredFloating (Floating UI), Portal+Z stacking scale,
useFocusTrap, useDismiss, usePresence/Transition, ShortcutProvider/useShortcut,
Field/Form/useForm, SortableList (dnd-kit), useVirtualList (@tanstack/react-virtual).
Tech locks installed: Floating UI, dnd-kit, Recharts, date-fns, react-virtual. The 6 overlays
(Menu/Dropdown/ContextMenu/Tooltip/Popover/Modal) were REFIT onto these as the reference impl.
Gallery is now registry-driven (components/studio/registry.tsx); a component registers via its
own folder's studioEntry. Two bugs fixed in verification: usePresence left overlays invisible
(single-rAF lost; split to two effects + timeout fallback), and useFocusTrap used offsetParent
which is null in position:fixed modals (switched to getClientRects). Authoring contract +
primitive usage documented in forge-suite/COMPONENT_AUTHORING.md. Browser-verified in Chrome 149.
Rules for modules (incl. the parallel build agents)¶
- Read tokens from
var(--bg|--panel|--card|--card2|--sel|--hover|--line|--txt|--mut|--mut2| --accent2|--pos|--neg|--grad); radiivar(--r-sm|--r|--r-md|--r-lg|--r-pill); fontvar(--font-sans). - Use the
components/uiprimitives; if a needed primitive is missing, add it to the kit (so all modules get it), don't fork it locally. - The finance module still has its own ported
.fin-rootCSS (#131314/Outfit) , it will be RE-SKINNED onto this kit; until then it's the one exception. - workspace.justinsforge.com itself is still the Flask app; when it's rebuilt in Next it joins this kit. The memory palette/font docs stay the human source of truth all reference.
Measured real-Notion values (app.notion.com, via getComputedStyle 2026-06-17)¶
Justin wants the Suite to match REAL Notion (his Flask clone already mirrors it). Measured:
- Surfaces: page bg #191919, sidebar bg #202020 (= our tokens). Topbar height 44px, bg #191919.
- Sidebar page rows: font 14px / weight 500, color #bcbab6, height 30px, radius 6px, no vertical padding (height from line-height + min-height).
- Section labels ("Favorites"/"Private"): 12px / weight 500, mixed-case (NOT uppercase), color #9b9b9b (dimmer than rows; = new token --mut3).
- Page title (H1): 32px / weight 700, line-height 38.4, color #f0efed.
- Body/base: system font stack, 15px base (workspace) / Notion uses 16px in places.
Method: navigate the page, document.querySelector Notion's .notion-sidebar-container / .notion-topbar / .notion-page-block, read getComputedStyle. elementFromPoint with screenshot coords drifts (sidebar scroll / dpr) , prefer real selectors. Measure per component as we build.
[Claude Code]