From 1483a4540696d186dd86fecdbef0511ed35de73c Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Sun, 12 Jul 2026 14:09:30 +0200 Subject: [PATCH] docs(product): commit design references under docs/product/reference/ Copy the founder's design handoff bundle (.proto/design/) into docs/product/reference/ byte-for-byte so dispatch agents running in git worktrees can read the HTML prototypes, veect-codebase/ prototype, upload PNGs, and remaining bundle files (ADR-029: reference only, never vendored into packages/). Ignore-list entries so whole-codebase auditors and formatters skip reference material: .prettierignore (byte preservation through lint-staged), .fallowrc.json ignorePatterns, root ESLint ignores, and the coverage:diff allowlist in scripts/coverage/diff.mjs (+ unit test). .DS_Store files skipped. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK --- .fallowrc.json | 3 +- .prettierignore | 3 + docs/product/reference/README.md | 22 + docs/product/reference/project/.thumbnail | Bin 0 -> 7508 bytes docs/product/reference/project/VEECT-SPEC.md | 251 + .../reference/project/Veect Landing.dc.html | 437 ++ docs/product/reference/project/Veect.dc.html | 4142 +++++++++++++++++ .../Weave - warm dark (backup).dc.html | 3730 +++++++++++++++ docs/product/reference/project/support.js | 1702 +++++++ .../project/uploads/company-context.md | 83 + .../project/uploads/design-app-prd.md | 177 + .../project/uploads/design-brief-weave.md | 274 ++ .../uploads/pasted-1783636165827-0.png | Bin 0 -> 362877 bytes .../uploads/pasted-1783637161167-0.png | Bin 0 -> 45920 bytes .../uploads/pasted-1783638818632-0.png | Bin 0 -> 18280 bytes .../uploads/pasted-1783642360921-0.png | Bin 0 -> 18280 bytes .../uploads/pasted-1783642379752-0.png | Bin 0 -> 12687 bytes .../uploads/pasted-1783642415560-0.png | Bin 0 -> 30084 bytes .../uploads/pasted-1783642433636-0.png | Bin 0 -> 30084 bytes .../uploads/pasted-1783695957008-0.png | Bin 0 -> 32262 bytes .../uploads/pasted-1783699051656-0.png | Bin 0 -> 37388 bytes .../uploads/pasted-1783706531590-0.png | Bin 0 -> 18701 bytes .../uploads/pasted-1783706579573-0.png | Bin 0 -> 12660 bytes .../uploads/pasted-1783706735275-0.png | Bin 0 -> 7317 bytes .../project/veect-codebase/.eslintrc.cjs | 17 + .../project/veect-codebase/.gitignore | 6 + .../project/veect-codebase/.prettierrc | 6 + .../project/veect-codebase/README.md | 87 + .../reference/project/veect-codebase/SPEC.md | 251 + .../project/veect-codebase/index.html | 18 + .../project/veect-codebase/package.json | 47 + .../project/veect-codebase/postcss.config.js | 6 + .../project/veect-codebase/src/App.tsx | 48 + .../src/components/atoms/Logo.tsx | 14 + .../src/components/atoms/primitives.tsx | 28 + .../src/components/molecules/ChatBubble.tsx | 36 + .../src/components/molecules/ChatComposer.tsx | 77 + .../components/molecules/CollapseButton.tsx | 30 + .../src/components/molecules/LayerTree.tsx | 121 + .../src/components/molecules/LibraryList.tsx | 73 + .../components/molecules/PolishIssueCard.tsx | 36 + .../components/molecules/SegmentedControl.tsx | 65 + .../src/components/molecules/TokenEditor.tsx | 38 + .../src/components/molecules/ZoomControls.tsx | 40 + .../src/components/organisms/ActivityBar.tsx | 123 + .../src/components/organisms/BoardCanvas.tsx | 87 + .../src/components/organisms/BoardSidebar.tsx | 83 + .../src/components/organisms/ChatPanel.tsx | 66 + .../src/components/organisms/CodePanel.tsx | 118 + .../src/components/organisms/ExportDialog.tsx | 78 + .../src/components/organisms/FrameNode.tsx | 89 + .../src/components/organisms/Inspector.tsx | 119 + .../components/organisms/IsolationView.tsx | 52 + .../src/components/organisms/NodeRenderer.tsx | 217 + .../src/components/organisms/PolishTab.tsx | 79 + .../src/components/organisms/TopBar.tsx | 84 + .../src/components/templates/EditorLayout.tsx | 82 + .../src/components/templates/PageShell.tsx | 62 + .../src/components/ui/badge.tsx | 26 + .../src/components/ui/button.tsx | 39 + .../src/components/ui/input.tsx | 31 + .../src/components/ui/panel.tsx | 19 + .../src/components/ui/select.tsx | 17 + .../project/veect-codebase/src/config/demo.ts | 28 + .../veect-codebase/src/config/library.ts | 30 + .../project/veect-codebase/src/engine/ai.ts | 116 + .../veect-codebase/src/engine/codegen.ts | 170 + .../veect-codebase/src/engine/color.ts | 62 + .../veect-codebase/src/engine/isolation.ts | 124 + .../veect-codebase/src/engine/parser.ts | 132 + .../veect-codebase/src/engine/polish.ts | 108 + .../veect-codebase/src/engine/starter.ts | 70 + .../veect-codebase/src/engine/system.ts | 37 + .../project/veect-codebase/src/engine/tree.ts | 73 + .../project/veect-codebase/src/engine/tw.ts | 61 + .../veect-codebase/src/engine/upload.ts | 28 + .../veect-codebase/src/engine/views.ts | 60 + .../veect-codebase/src/hooks/useAiCompose.ts | 115 + .../src/hooks/usePanelResize.ts | 35 + .../project/veect-codebase/src/index.css | 74 + .../project/veect-codebase/src/lib/utils.ts | 18 + .../project/veect-codebase/src/main.tsx | 10 + .../veect-codebase/src/pages/EditorPage.tsx | 18 + .../veect-codebase/src/pages/HomePage.tsx | 114 + .../src/pages/OnboardingPage.tsx | 108 + .../veect-codebase/src/pages/ProfilePage.tsx | 73 + .../veect-codebase/src/pages/SettingsPage.tsx | 102 + .../veect-codebase/src/pages/SignInPage.tsx | 36 + .../src/pages/SystemUpdatePage.tsx | 116 + .../project/veect-codebase/src/store/veect.ts | 197 + .../project/veect-codebase/src/types/index.ts | 136 + .../project/veect-codebase/tailwind.config.ts | 40 + .../project/veect-codebase/tsconfig.json | 21 + .../project/veect-codebase/vite.config.ts | 10 + eslint.config.js | 2 + scripts/coverage/diff.mjs | 3 + scripts/coverage/diff.test.mjs | 20 + 97 files changed, 15685 insertions(+), 1 deletion(-) create mode 100644 docs/product/reference/README.md create mode 100644 docs/product/reference/project/.thumbnail create mode 100644 docs/product/reference/project/VEECT-SPEC.md create mode 100644 docs/product/reference/project/Veect Landing.dc.html create mode 100644 docs/product/reference/project/Veect.dc.html create mode 100644 docs/product/reference/project/Weave - warm dark (backup).dc.html create mode 100644 docs/product/reference/project/support.js create mode 100644 docs/product/reference/project/uploads/company-context.md create mode 100644 docs/product/reference/project/uploads/design-app-prd.md create mode 100644 docs/product/reference/project/uploads/design-brief-weave.md create mode 100644 docs/product/reference/project/uploads/pasted-1783636165827-0.png create mode 100644 docs/product/reference/project/uploads/pasted-1783637161167-0.png create mode 100644 docs/product/reference/project/uploads/pasted-1783638818632-0.png create mode 100644 docs/product/reference/project/uploads/pasted-1783642360921-0.png create mode 100644 docs/product/reference/project/uploads/pasted-1783642379752-0.png create mode 100644 docs/product/reference/project/uploads/pasted-1783642415560-0.png create mode 100644 docs/product/reference/project/uploads/pasted-1783642433636-0.png create mode 100644 docs/product/reference/project/uploads/pasted-1783695957008-0.png create mode 100644 docs/product/reference/project/uploads/pasted-1783699051656-0.png create mode 100644 docs/product/reference/project/uploads/pasted-1783706531590-0.png create mode 100644 docs/product/reference/project/uploads/pasted-1783706579573-0.png create mode 100644 docs/product/reference/project/uploads/pasted-1783706735275-0.png create mode 100644 docs/product/reference/project/veect-codebase/.eslintrc.cjs create mode 100644 docs/product/reference/project/veect-codebase/.gitignore create mode 100644 docs/product/reference/project/veect-codebase/.prettierrc create mode 100644 docs/product/reference/project/veect-codebase/README.md create mode 100644 docs/product/reference/project/veect-codebase/SPEC.md create mode 100644 docs/product/reference/project/veect-codebase/index.html create mode 100644 docs/product/reference/project/veect-codebase/package.json create mode 100644 docs/product/reference/project/veect-codebase/postcss.config.js create mode 100644 docs/product/reference/project/veect-codebase/src/App.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/atoms/Logo.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/atoms/primitives.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/molecules/ChatBubble.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/molecules/ChatComposer.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/molecules/CollapseButton.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/molecules/LayerTree.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/molecules/LibraryList.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/molecules/PolishIssueCard.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/molecules/SegmentedControl.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/molecules/TokenEditor.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/molecules/ZoomControls.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/organisms/ActivityBar.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/organisms/BoardCanvas.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/organisms/BoardSidebar.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/organisms/ChatPanel.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/organisms/CodePanel.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/organisms/ExportDialog.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/organisms/FrameNode.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/organisms/Inspector.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/organisms/IsolationView.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/organisms/NodeRenderer.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/organisms/PolishTab.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/organisms/TopBar.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/templates/EditorLayout.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/templates/PageShell.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/ui/badge.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/ui/button.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/ui/input.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/ui/panel.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/components/ui/select.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/config/demo.ts create mode 100644 docs/product/reference/project/veect-codebase/src/config/library.ts create mode 100644 docs/product/reference/project/veect-codebase/src/engine/ai.ts create mode 100644 docs/product/reference/project/veect-codebase/src/engine/codegen.ts create mode 100644 docs/product/reference/project/veect-codebase/src/engine/color.ts create mode 100644 docs/product/reference/project/veect-codebase/src/engine/isolation.ts create mode 100644 docs/product/reference/project/veect-codebase/src/engine/parser.ts create mode 100644 docs/product/reference/project/veect-codebase/src/engine/polish.ts create mode 100644 docs/product/reference/project/veect-codebase/src/engine/starter.ts create mode 100644 docs/product/reference/project/veect-codebase/src/engine/system.ts create mode 100644 docs/product/reference/project/veect-codebase/src/engine/tree.ts create mode 100644 docs/product/reference/project/veect-codebase/src/engine/tw.ts create mode 100644 docs/product/reference/project/veect-codebase/src/engine/upload.ts create mode 100644 docs/product/reference/project/veect-codebase/src/engine/views.ts create mode 100644 docs/product/reference/project/veect-codebase/src/hooks/useAiCompose.ts create mode 100644 docs/product/reference/project/veect-codebase/src/hooks/usePanelResize.ts create mode 100644 docs/product/reference/project/veect-codebase/src/index.css create mode 100644 docs/product/reference/project/veect-codebase/src/lib/utils.ts create mode 100644 docs/product/reference/project/veect-codebase/src/main.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/pages/EditorPage.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/pages/HomePage.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/pages/OnboardingPage.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/pages/ProfilePage.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/pages/SettingsPage.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/pages/SignInPage.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/pages/SystemUpdatePage.tsx create mode 100644 docs/product/reference/project/veect-codebase/src/store/veect.ts create mode 100644 docs/product/reference/project/veect-codebase/src/types/index.ts create mode 100644 docs/product/reference/project/veect-codebase/tailwind.config.ts create mode 100644 docs/product/reference/project/veect-codebase/tsconfig.json create mode 100644 docs/product/reference/project/veect-codebase/vite.config.ts diff --git a/.fallowrc.json b/.fallowrc.json index 785ae90..58c70ac 100644 --- a/.fallowrc.json +++ b/.fallowrc.json @@ -9,7 +9,8 @@ "**/__snapshots__/**", "**/turbo/generators/templates/**", "**/*.generated.ts", - "**/*.d.ts" + "**/*.d.ts", + "docs/product/reference/**" ], "dynamicallyLoaded": [ "packages/**/__factories__/**", diff --git a/.prettierignore b/.prettierignore index d230ed6..d42c1c8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,5 @@ # Generated compliance artifacts — do not reformat compliance/*.yml + +# Design reference material (ADR-029) — committed byte-for-byte, never reformat +docs/product/reference/ diff --git a/docs/product/reference/README.md b/docs/product/reference/README.md new file mode 100644 index 0000000..8f8bce6 --- /dev/null +++ b/docs/product/reference/README.md @@ -0,0 +1,22 @@ +# CODING AGENTS: READ THIS FIRST + +This is a **handoff bundle** from Claude Design (claude.ai/design). + +A user mocked up designs in HTML/CSS/JS using an AI design tool, then exported this bundle so a coding agent can implement the designs for real. + +## What you should do — IMPORTANT + +**Read `app-prototype-development/project/Veect.dc.html` in full.** The user had this file open when they triggered the handoff, so it's almost certainly the primary design they want built. Read it top to bottom — don't skim. Then **follow its imports**: open every file it pulls in (shared components, CSS, scripts) so you understand how the pieces fit together before you start implementing. + +**If anything is ambiguous, ask the user to confirm before you start implementing.** It's much cheaper to clarify scope up front than to build the wrong thing. + +## About the design files + +The design medium is **HTML/CSS/JS** — these are prototypes, not production code. Your job is to **recreate them pixel-perfectly** in whatever technology makes sense for the target codebase (React, Vue, native, whatever fits). Match the visual output; don't copy the prototype's internal structure unless it happens to fit. + +**Don't render these files in a browser or take screenshots unless the user asks you to.** Everything you need — dimensions, colors, layout rules — is spelled out in the source. Read the HTML and CSS directly; a screenshot won't tell you anything they don't. + +## Bundle contents + +- `app-prototype-development/README.md` — this file +- `app-prototype-development/project/` — the `App prototype development` project files (HTML prototypes, assets, components) diff --git a/docs/product/reference/project/.thumbnail b/docs/product/reference/project/.thumbnail new file mode 100644 index 0000000000000000000000000000000000000000..d9ac15f67b79e46c6c9fbda8c3557a378fc1ca3b GIT binary patch literal 7508 zcmdT}Wl&wqmfpAqcM0weL4pLr2?Td{mxDV2g1fsUI48kfLU8v0!QJfy3xq>(hTJ=^ z-mR%O^MAUlSAV_w+uf_z>h9XLx0vHaI?li}LWc=Ss zGz&{N^C!yBr$X=gH2ITF1W%a2`ftqq2b=wkMgL$gcW3t}j>;eGs-XsY!WK`M+4>*Y z>>t?N+4T=U_K73vVDIr~u0QF|>M$)GHPxSL)TcrLxB+UwTR`&9`k&fQ$*B+kgkS)G zkoa$%Sr!0%2?GF z06^FOtM5ttFSb!Xbx}Oo<@A)T0eip_pax_CN5CB5d_uecH^2i3JVlt=h#UZLz5_XM ztby>_@UI*MGGys#3n4VzB|ih7McF!Du{ZEeP}+)J4g^xzl&8zdkE3jzdGc#bPLl44 zU!g$uMNxD+^HCJASCB24G9P;{M<1$hBQ%S<+rRD-;=S5=OxR;O%7_V|fT7+6TqoRA z9#hJXVJwI@1q|IL!d}j(_Zf9jjf+$Qg+-(^#Hx z?*1in@R2lc85V4;-A6f6J=vb}wTK=;_9^1A1iD@98$`|+LP**0<8anMeW2biqvl=`vlAOwe9ha_ zp7>O`kb@Bo>^ISpd2yvTzhr2ks|a0&j8l8XPI*zx_m?JNKA1g^@&`r=mL-PKX=Z$;VzjbZypoLppf8Wcl9P zRUwZ*`m@6biNc~@t!RF;Y z+m+|NWT&Sx(nb-TdPBF9Co0{Rz{xkyGXWTrDthQ1yPACDJ&wU6x`-$z)r!{mZh_^>v6A{NMb z)Dg+U_BLY-ozMtq&n_F(7lps&Y?I5(qt$p)AMp=W45rI%3aC`|Z)Fn`@;P4(RG>Wr_1bhe zUO7T(7lpwN54lTQ3Hg8ab}xxJ=ASc5gnICG z)B)J|7ah+JJ0Dy74+*4}H6iqHsuONW=0HE`evt1MZ>P zY;OM9i{sAFj@c%Yb!D`t+e6Mv z(RY6b;wdIcN{XMQjZJQ-|LMDbxbgoB92gJgc=gY#)4@fps0Y;u03LqN1;~evY^Ogb zwkU{Xo|q~5*gY>NLuD)i0}qt8gK(IN(tQX={AO@uNy6L=792_(kBhHq>pM=%mcm7= zRclep!VdYps!mOCPC~=W3`h+%wFBn z&7rO#AocYQkFc8}A@=k@Fs@GYlS()#$FsCN*~c$kzuSb@GrmaUt6E>Y&>cJxUuc8X z#voWU+6^DGLau_o4P6l}1lin5j$%YCl@^R`RG1mOFSn;dIx)EE^V|;@bh0h@_%k!! zyV^-(@brC78e}MLGJ|XeYb8dsBX2ByP{%bi$t3WwmNW6!y2!ha1O`*GsEqV1V}84p zdZR7@2vd^ZTO*&tl4q2t(<{J7WH~G~yv0J7yK#9dSM1ZD?OCH=8pFpSULfL+6mA%) zuX^6|mr$*v3Q#w7mgR20qeU|vV99V@9Ps|}d*+UROSFPa>i_jjOS`>jZr zVgkV-lKeqPo2iM(<=yr{cwgdR14gzUE|cH<%m;}2=c;<|omkjRKygaWYn7W;pV?b) z5h-9Y=uB>#hD4149-Z?eUU7_9X+!IkF@5im$AR3NvJa`ul^(0;EaDd)tzl#LXdFxf z5B88buvFjn4d@^a@g1}s4x2%kx7qHqUIkg@>ssvNNAi)&ACjg>RQBtfq5GmCzq0IRNg=I(~O`#7)AQxkrJCBJS*Nx>f_i8vxC+Gv>&yNyxo z4Ht82cWwV(qpr_AEMlf1v&Q->>)f&*ewYgDdnINt3VENt0~@RA%e-*%3-{1vb>78f zT!)hFfyHQ(Qoye*Q>Hy5?_8?+=OEt^gT~@h6P9mR-}!`qWR8)R%D|LNJ=|r(THF4S zdh}<}YM;vvNpK?HF`yXS+n^C>BdkqPZ=jyW6X{SpL0K5N7vz6-D=vJ&jy{aDsjv`x zrFg+qGB7nO;G^Sikx)M<9veg%4S>Cu66-(mc9EQYT*WDS%@#FaPt7}g20A5B%?;Z) zn)u#Ot-WrqMqQ);My(2I7Bh>qpP}E!@ zBtT((_o4dmSq&G#Dvd2#v6r84gP2hgJd!7$WvcfBn%u^M9t!V=R*5w`tcv4@uC9`o z&P?s4Xk8uwv|{B!P>l6@Qjwftr;nY7 zmGH83HGIK$-RI6cm*teF>mNae&!%UjG1$r;QKmT+Kd!I>y`p-N(8OPO3ha*Bf|@ZA~dW%9NVmuxvF`qI*?pwZ4zQ`ImW zl|srjX238WKy+uqkem>{BDSZ23xr1mqAE!H_n>J$h0Y5JLd6}SZp;bJt`y|#1 zdBojpLew}5+6uNY`#m3$u;bUc9FYva2UX!g>;2zwC*IR54go%QogWHG8FY}($C>(B z-!G$1mok01V^2D_%y7#G+TjGWtmTcUe_KM48Ygj{UL=ai3GOz2a&W~mPnN3$3dAhi z9G^hWrd4&qmdSa&epuN~;6Dx$4|M&64qQ{6b>x>%a~~?MS_(u^DHQ}3RrJ&?%;%~f z2+!X`w|b0H(Hv?NjU6AEyNYR?QXruj_~g|~!*dXey|r(bJDRnlUk6XYjoVdj7~_H= z8dd8QNNwD>i-fV5*IjC|2*;FD^?`|o+g~olbi*EbvJZ;yH4b_2aq=iv-#gaJiQi8MR}sNq?RQCvYbVM(@2&be+44fAD5A9LIVLkVqU{m*mo>ZDNJgQ-i3AB= zC-@oB+AxC~a`}ZraahD%Mr%>%(AUCpdwedEp<|>O1t1NUN2(<|TiUa~F2H3=a`W1S zA{ZkiasLsR3%GStS-iUFmcZ|8c-wmBnM$|xW!4|k8qzFG_g1t>x-AagBCcm%sM|Vd z>{-<9GN`xb_!!AaQd6L5!B`9Jd2vY2@2xnTqawrB2ZUlLvsBaF_u ztmp{FK?KW4N;mk78a~*W?H3p3je8rn*V*<8)!_Nn2xDpOi{!P}s;D<4xB82(xE%~O z1Sr@aodw{&!a(?RvUv#5O{^g2jf9Ohg2E%#!mCx^{?2gipxzTAGr-@T$G`~&lU!uZ zUS%Q72Xdi0?i+FxF6DNgF1@%>UNXjhR;!2Cs?u6$44Y;sR@tp8ujEBIC@t>x5A7~@ z8OfH$BbFdk0>TdgNdq@cFYr|-tgQ4HckfE6pkj5Zp{Th;ItviG6hqi z%yjsu%!F#c4!i%R8PHeGvTF@P<&IJ!p%`%9UTsT#m=wfp#7+M30e*Enq1Jvk40jATET9%_W}^$bbZs+|AWps;_8ol2j`}Z+6pO>1zAW>0iuP1(}1xk3TwO7%@o_ za$5|RNeZPZ&~|>1wK8y>omNvQOl(l$a%tNjsz9Z9IEgA1v1~#rc_n|qviXo$S3QhN z+T5HuO|Q=-k|K&O zPaO5y7=6SAtZ$9MmBhztVOtnwx7I0$ry-T4wL>~kd@lbjW246yS!qxYh-`<4^8*G3 z78-bal@KpS*zkHJOlpp|!`|onAa}AGWBaEel0>F3opKBEIi>F7FTJ_^BdIm|m&Qk2 zj__DAp5@6E$cPd3l!i`+ImHe>A+hif9_VDnNhJ>TZI)>18xHoga?IQK6=O#$Xz-06 zu3MznaKA>lFK4VpOhB`+9F5ErY8U)zymEf!;x!g)Wu#&N0Te$amnVnJ6O)?hA z(le*~u{0p+pU(!ce!#(j)w-}A)oVrhIjDlOPTXc)`|lc4eY%P_i5y!Kci;-w+!!`| z#b<1|Mjyg8W>#IC=&c+}&8-+w%j*;6%4|oL9zx2020d?Y(wyRctnqV=@*Li|6XI3x zliHT_@5+1Sch%%~t_(ssejXGqb;Csa*bL6RhAx$y5f-#{`P+iaYWT8Ev6WT^KI^$x zG_h;ouANJshlm`QkfDQn`d5-YJl=VW`yAjPtX3Y>4-*PhgN&Q2a$53)-Z0S1D%>Gat?3hmaHRf>+r2Z+@6>rl-!p8Hi zJ*Hy$4ndY%?0AeSyCHbYG;En!&r4buvB_h67hM^LqYJG;6Ry5nU8&zUpYRerD*0%U z6Tj_w99HvW(=%hxSF9)pG79vz_xzSF`nrxPt18zN~^LezH-$+g) zzIO!&Nb1)?a$}^cFU(5!?X}J$5B%A2gW+qXe7y2SexdCG3@i z_BWwDNbjDHlOBHZiT|o!2P*I6(keE$K1FJJtQ{d&dGER|x`?|Yw({sCM+^g|$`}#3 zOS@d>@_F)FD>#m_zi|a6RO{o@Dc_&uAW9^$eM+$kgg3rYz0tiWbNEx_d*3p?TJ8!6$cJyMiZLhu_ttiGdqyWoH|AlY$ciP;mO_yBHm*)`$j;7b` z5l*a3vrJ4=d4<>c%deo{qb0~np7U7gcJtADF$|z@7=Y*#IQ0(gaGpNPw#Zu5PEGI5 zDQEe?zB?2Q;_Nh)o-krOS%0H>)h@hF07`Kpf}08;8sUSQGQ+)Y)TIfUVZ;D8WSPVi(?SS%kUgVY*7Ta zk6r$N?{rH=8S>1v0x_Ny>b?e1VSyf_HNkYN+|`7_on$Iuq!&d5yiR<9LCDbQE%kkz zFWY(egmm36r5V6+7YDigskY{*l^)~6$S(3xLcbb$D|CO?zrmgAv1ow?sYhFuQSkDr znGQ;J6T&#szcQJs@t^!A<^sPn`Q)D^ zZBSZOW~Yu!yxGj=`5>NvsiPKD+bIxHeQ+iFd>pQ;gu+H3aXQEkaUSTrgmmcf$xE*1vjv^ zof)O2B%U+!t+5f6bWvYm|2z@Q8qO+RDLxVS5^~XceYf_IkzFPnRe#;K8SjCn5!s{T zL*#I#GE5f+YKw5HeKkr(y;m(t6;=I99CiL<9a_HN{0&B0-62iXVs$(Mxcn+1oH>Q= zV@4(9y*=65OsF`IX-rTdmy*s@bkL)J5dVA(Ap#bkz_|^>@FAJ#P)8Nu1X}n4UCEgQ zXmY^ujVKEan-C0J9U(?kv6Fo%WT8t1Q%2C>x~=fp6!iCOviPBQA6wnnqz;H>5)@To zlgHb#MjdkFOzn4Vx5SgT=PGr%>%U|Gsm^#n9QCfLaIcG9aSFojw505=M1aZ-$ z#OczCBu53B*UB+YryqW}-ML_-TP4>hDlb&V6cJu1`uJ@6h1MAbWmce22I0iqSF&~Y z`jF-$(MPMhYZclwQM=q`kY%a#Lg`$IK4R0_qQ{D7oF)NyIR8b$l(Jrox#^&8BwQ~F zMWEK}k5!_OWtsx!y=1WguB83R-T`yxtur{!b_aoGl$Km?2-i^L6b`n=SC)D^q_&Zc zU-na@Q~FaDl-ZKF9s@nhPG89hd*sl6B~;woI#N&VWa~;+eIcMtn6y>B#E_}jCQbj| zScc27LUtzWq9(BX3136#Wv<Emhi%t?=#lbvdz%+(}I`Vm2*H9|+y}5@4?3El<`geF7#zM&T$tQf$a6SOb@YqxZzfUlf0glu zjLTz3(J{~FIc%mh%K4|=_$(Ug@~CLAH9OlDG|g61w1pj2D^N^CK2|6szvG+5V@AC3 z`|S@42C@4@tW{7G=LjI5eYm=26lBQ2iJlc9wj0UUM@+lT6eMC9?n?l_p~M#oxZ(cYTmk>)|0t> z#Tyu0eJA;WY<0Aes*kl+Gpp0!0oJ&B+e->v3NW)9E#G3FQBHjEn0ac@qWUJQm}TBf zhdSrUGcjLPr)mX9uw*%8v%&_-tl1nGt%89VQIiFAF%`jQ(D#ErivtwlO90$`?6h(v ztur0UzVxhnc1DA*fNOG*rjWsqQInq@S5y}YU)L3S{;dtg4e^^a>3$pmPRGJNA#V@W zg)!@#s-g2=9^-V$gD7r+lUeW>QIu6TMvIFp58OY-Ov~C|bA;*It`@aV@)r?aADEP6 z`c?E^AJ+BSmD;!jcOS{52A^Cs*Yg^KSCti~s=f;QKnW^d_Sqe`JY!a;j9QE}vNP~+ z=mZyjm<`<4ng zN_d`r7|MSS;P8Hdn*i*DtZ9X(#%VVU127eDTBJtMkpa|lh$B)Iy@Pj4a1rjQAn7#? z+b5dLdJM;bw*!15t&%qsnrSz@$+(}wBciU-Ar8uM>NNEhE?Tt!kjzHF;ppryy}_?O zXtej`@=fmx/

` + Tailwind | size/weight/tone classes, neutral ramp | +| button | ` + + + {{ brandHex }} + +

+ radius + + {{ radiusLabel }} +
+
+ + + +
+ +
+
+ Get started + Docs + + NEW +
+
+ Email +
you@company.com
+
+
+ + Notifications + + your tokens · live +
+
+ + + + +
+
+
+ 0 + UNREGISTERED ELEMENTS
ACROSS A 100-PROMPT EVAL
+
+
+ <10% + LOC CHANGED PRE-MERGE —
THE ENGINEER IS THE JUDGE
+
+
+ <20 min + SIGN-IN TO YOUR FIRST
EXPORTED, ON-BRAND SCREEN
+
+
+
+ +
+
+ WHY IT HOLDS +

Other tools re-invent your UI. Veect is constrained to it.

+
+
+ 01 — YOUR COMPONENTS + The canvas renders the real thing + One runtime backs canvas, preview and export. "Roughly how it'll look" is the lie that makes handoff lossy — Veect never tells it. +
+
+ 02 — YOUR TOKENS + Change once, everywhere + Edit brand-500 and watch every bound component ripple on canvas and in code — the system stays the single source of truth. +
+
+ 03 — REAL IMPORTS + Code an engineer would merge + Export imports your mapped library — import paths, prop names and all. Not inlined clones. That is the whole trust bar. +
+
+
+
+ +
+
+
+ THE GRACEFUL REFUSAL +

When AI can't stay on-system, it says so.

+

Ask for something your registry can't express and Veect refuses — then offers to compose it from your primitives. It never fails open to generic markup. Across a 100-prompt eval: zero unregistered elements.

+
+
+
+ A hero with a spinning 3D globe +
+
+
+ You don't have a component for this yet. + + Compose from primitives + Not now + +
+
+ an error state, elevated to a signature moment +
+
+
+ +
+
+
+
+ Home.tsx + + + in sync +
+
+
import { Button, Card, TextField } from "@solstice/ui";
+
 
+
export default function Home() {
+
return (
+
<main className="flex flex-col bg-white">
+
<Button kind="primary">Start free</Button>
+
</main>
+
);
+
}
+
+
+ prettier ✓tsc --noEmit ✓imports your library +
+
+
+ TRUST IS EARNED IN THE OPEN +

The code is a keystroke away — and it's yours.

+

Nudge a component and the code updates in lockstep. Deterministic output — same tree, same code — that passes Prettier, tsc and your reviewer's patience.

+
+
+
+ +
+
+
+ POLISH — THE IMPECCABLE PASS +

On-brand and genuinely well-designed.

+

One pass critiques spacing, hierarchy and contrast against a craft standard — and fixes them in your tokens, with the why. Try it.

+
+ + {{ item.label }} + +
+
+
+
+ New — shared team calendars + Scheduling that feels like sunlight + Every timezone, handled kindly. +
+ Start free + Book a demo +
+
+
+ + {{ polishNote }} +
+
+
+
+ +
+
+

What you design is what ships.

+ Open the canvas → +
+ + veect + + working name · design-partner MVP + + tokens in · react out · impeccable by default +
+
+
+ + + + + + diff --git a/docs/product/reference/project/Veect.dc.html b/docs/product/reference/project/Veect.dc.html new file mode 100644 index 0000000..de74167 --- /dev/null +++ b/docs/product/reference/project/Veect.dc.html @@ -0,0 +1,4142 @@ + + + + + + + + + + + + + + + + +
+ + +
+
+ + +
+ veect + design-system-native canvas +
+

Connect your repository.

+

Veect reads your components and tokens straight from the source — then composes production React on a branch you can publish. Nothing to paste, nothing to upload.

+
+ + + +
+
+ +
+
+ + + +

Authorize Veect on GitHub

+

Enter this code at github.com to grant access. Veect requests the narrowest scope that still lets it read components and open pull requests.

+
+
+ DEVICE CODE + + Approve in browser → +
+ + waiting for approval… +
+
+
+ +
{{ cxErr }}
+
+ + + +
+
+
+ + + +

Your org hasn't authorized Veect yet

+

Sign-in worked, but solstice/web is behind an organization policy. An owner needs to approve Veect once — then every seat can connect.

+
+
+ You can + Open the org's third-party access settings and approve Veect, or send the request to an admin. +
+
+ Open approval settings → + +
+
+ +
+ + + +

Confirm the host key

+

First connection to git.solstice.dev over SSH. Check this fingerprint matches what your host published before trusting it.

+
+ ED25519 FINGERPRINT + SHA256:kR8Hq3vL0pR2mNbXcZ1wY7tSh6dKfJ4aQeUoIgP5s +
+ + +
+
+
+ + +

Trust this repository?

+

Veect will run {{ cxRepoName }}'s install scripts and dev server on your machine to render real components. Only connect repositories you trust.

+
+ + + + the “without scripts” choice is remembered per repository +
+
+ + +

Connecting {{ cxRepoName }}

+

This runs on your machine and can take a few minutes on first connect. You can leave it running.

+
+ +
+ {{ item.tick }} + {{ item.label }} + {{ item.note }} +
+
+
+ +
{{ cxErr }}
+
+ + +
+
+ +
+ + + +
+
+
+
+ + +

Point Veect at your components

+

No veect.config.json yet — pick the folders to read. This config rides your first published branch.

+
+
+ COMPONENT FOLDERS + + + +
+
+
+ APP DIRECTORY (MONOREPO) + +
+
+ DETECTED + +
+ + {{ item.k }} + + {{ item.v }} +
+
+
+ +
+
+
+ + +

Environment

+

Your preview needs a few keys. They stay on this machine, encrypted — Veect never sends them anywhere.

+
+ +
+ {{ item.key }} + {{ item.src }} + + {{ item.chip }} +
+
+
+
+ + + +
+
+ + +
+ +

Reading your repository…

+
+ +

Your system is live.

+
+ {{ revealStat }} +
+
+
+
Get started
+
Secondary
+
+
+
NEW
+
Filter · 3
+
+
+ Email +
you@company.com
+
+
+
+
MK
+
+ Maya Kade + Product designer +
+
+
+
+
+
+ Overview + Members + Billing +
+
+
+
+
+
+
+ Notifications +
+
+
+
+
+
+ volume — 62 +
+
+ Ag + {{ revealFontNote }} +
+
+ +
+ + ⌘K anywhere · ✦ to compose +
+
+
+ +
+
+
+ + +
+ +
+ veect +
+ + +
+
+
+ +
+ Solstice + workspace · solstice/web · main +
+
+
+ PROJECTS · EACH ON ITS BRANCH + + + + +
+
+ + +
+
+ + + +
+
+
+
+
+ + + + {{ saveLabel }} + + +
+ + + +
+
+ + + + + + + + + +
+ +
+ +
+ + +
+
+ + + +
+
+ + +
+ +
+
+ +
+ {{ leftTabTitle }} +
+
+ + + + +
+ + + +
+
+ + + +
+
+ + +
BASE KIT — MAPPED TO YOUR CODE
+ +
+ {{ item.glyph }} + {{ item.name }} + + + +
+
+
PATTERNS — YOURS
+ + +
+ + {{ item.name }} + +
+
+
+ +
Save a selection as a pattern — it lives here, in your system.
+
+
CUSTOM — YOUR CODE
+ +
+ +
+ {{ item.name }} + {{ item.file }} +
+ + +
+
+ + +
+ + +
COLOR — BRAND
+ + +
+
+
+ + + +
+
+ + +
+
+ edits ripple across canvas + code + +
+
+
+
+
+ + + +
+ {{ whereUsed }} +
COLOR — NEUTRAL
+
+ + + +
+
TYPE
+
+ Ag + +
+
NEUTRALS
+
+ + + +
+
RADIUS
+
+ radius-md + + + {{ radiusChip }} + +
+
SPACING
+
+ + + +
+ 4 · 8 · 12 · 16 · 24 · 32 · 48 +
+ +
HISTORY — EVERY CHANGE, LABELLED
+ +
No steps yet — every edit lands here with a label. Click one to travel back.
+
+ + + +
+ +
+
+
+ +
+
+ {{ canvasEl }} +
+ +
+
+ + + + +
+
+ ISOLATION + {{ isoName }} + {{ isoMap }} + + real runtime · variants × states + +
+
{{ isoEl }}
+
+
+ +
+ {{ bubbleTag }} + + + +
+
+
+
+ + + +
+ + + + + scroll — pan · ⌘ scroll — zoom · drag board — pan + +
+ +
+
+ + + + + + + + +
+
+ +
+
+
+ +
+ + COMPOSE — CONSTRAINED TO YOUR SYSTEM + + + +
+
+ +
+ +
+ + Describe a section — Veect composes it from your mapped components and tokens only. +
+ + + +
+ ◎ pick — scope an edit to one element
✎ draw — scribble a note on the board
+
+
+ +
+
+ {{ item.roleLabel }} + +
+ + ▧ {{ att.name }} + +
+
+ {{ item.text }} + +
+ + + staged as a ghost — nothing applied +
+
+ +
+ + +
+
+
+
+
+ +
+ + composing from your system… +
+
+
+
+
+ +
+ + + +
+
+ +
+ + + +
+
+
+ + + + + + +
+ + +
+ + + + ▧ {{ att.name }} + + + ⏎ send · esc dismiss + +
+
+
+
+
+
+ + +
+ +
+
+ +
+ {{ rightTabTitle }} +
+
+ + + +
+ Select a component on the canvas. +
click — select · drag — move
⌘K — commands · ✦ — compose
⌘Z — undo · ⌫ — delete
+
+
+ +
+
+ {{ selGlyph }} +
+ {{ selName }} + {{ selType }} +
+
+ + +
+ FRAME + +
+ width +
+ + {{ frameWVal }} + +
+
+
+ active view + {{ frameViewInfo }} +
+
+
+ +
+ CONTENT + +
+
+ +
+ VARIANT +
+ + + +
+
+
+ +
+ SIZE +
+ + + +
+ WEIGHT +
+ + + +
+
+
+ +
+ TONE +
+ + + +
+ {{ toneNote }} +
+
+ +
+ LAYOUT +
+ + + +
+
+
+ gap +
+ + {{ selGap }} + +
+
+
+ padding +
+ + {{ selPad }} + +
+
+
+
+
+
+ + + +
+ {{ item.cat }} + {{ item.selectEl }} +
+
+ +
+
+
+ TOKENS BOUND + +
+ {{ item.prop }} + + {{ item.tok }} +
+
+
+
+ + + + + +
+
+
+
+ + +
+ +
+ + Critique this screen against your craft standard. + Spacing, hierarchy, contrast, alignment, states — every fix lands in your tokens and components, with the why. + + standard: Impeccable +
+
+ +
+
+ Scanning spacing, hierarchy, contrast… +
+
+ +
+ {{ polishCount }} + + + +
+ +
+
+ {{ item.sev }} + {{ item.title }} +
+ {{ item.why }} + +
+
+
+ +
+ + Impeccable. Nothing to fix. + Every check passed against your standard. + +
+
+
+
+ +
+
+
+ + +
+
+
+ + + + + + + + + + +
+ +
+
{{ codeDraftEl }}
+ +
+
+ +
{{ codeEl }}
+
+
+ prettier ✓tsc --noEmit ✓imports @solstice/ui +
+
+
+ +
+
+
+ + +
+
+
+ veect +
+

Sign in.

+

Your system is waiting — everything you compose exports as real React.

+ + +
+ + or + +
+ + + design-partner MVP — accounts are provisioned manually +
+
+
+ + +
+
+ veect + + + + +
+ + +
+
+ WORKSPACE · ONE REPO EACH + + + +
+ +
+
+
+ +
+
+
+
+ {{ connIcon }} +
+
+ Solstice + {{ connAccess }} +
+ workspace · one repo · solstice/web · {{ connMeta }} +
+ +
+
+

Projects

+ {{ homeMeta }} +
+
+ + + + +
+

Design systems

+
+
+
+ + + +
+
+
+ Solstice + {{ homeSysMeta }} +
+ + +
+
+ +
+
+
+
+
+ + +
+
+ + Settings + + +
+
+
+
+ CONNECTION +
+ {{ connIcon }} +
+
+ solstice/web + {{ connAccess }} +
+ {{ connMeta }} +
+ + +
+
+ + Tokens & components discovered from this repo — no paste, no upload. + {{ setSysMeta }} +
+
+
+ ENVIRONMENT +
+ +
+ {{ item.key }} + {{ item.masked }} + {{ item.src }} + {{ item.chip }} +
+
+
+ stays on this machine, encrypted · Veect never transmits your keys +
+
+ DOCTOR — CONNECTION HEALTH +
+ +
+ {{ item.dot }} + {{ item.name }} + {{ item.detail }} +
+
+
+
+
+ COMPONENT MAPPINGS — THE MERGE BAR +
+ +
+ {{ item.glyph }} + {{ item.name }} + {{ item.path }} + +
+
+
+ unmapped components fall back to the Veect base kit at export — the warning follows them +
+
+ CRAFT STANDARD — POLISH RULESET +
+ + + +
+
+
+ EXPORT DEFAULTS +
+ +
+ {{ item.label }} + {{ item.note }} + {{ item.chip }} +
+
+
+
+
+ PLAN — DESIGN PARTNER +
+
+ $0 during the MVP + billing starts at GA · seats 2 of 5 used · no card on file +
+ +
+
+ +
+
+
+
+ + +
+
+ + Profile + +
+
+
+
+ M +
+ Maya Kade + maya@solstice.team · Product designer +
+ DESIGN PARTNER +
+
+ PREFERENCES +
+ App theme +
+ + + +
+
+
+
+ WORKSPACE — SOLSTICE +
+ +
+ {{ item.initials }} +
+ {{ item.name }} + {{ item.role }} +
+ {{ item.chip }} +
+
+ +
+
+
+ KEYBOARD +
+ ⌘K — commands · ✦ — compose · ⌘Z — undo + ⌘0 — zoom to fit · ⌘1 — 100% · scroll — pan · ⌘scroll — zoom + ⌫ — delete selection · esc — dismiss anything +
+
+ +
+
+
+
+ + +
+
+ + System update + re-import tokens · diff · apply with undo + +
+
+
+ +

Devon updated the tokens. Paste the new file — Veect diffs it against the live system and shows the impact before anything changes.

+ + +
+ +
+ +
+ {{ item.tag }} + {{ item.tok }} + + {{ item.from }} + + + {{ item.to }} +
+
+
+
+ i + {{ sysImpact }} +
+
+ + +
+
+ +
+ + System updated. + radius-md 12 → 10 rippled across every bound component on the canvas and in the code. Bindings preserved — nothing broke silently. +
+ + +
+
+
+
+
+
+
+ + +
+
{{ previewEl }}
+
+ + preview — real components, real states + + +
+
+
+ + +
+
+
+
+ {{ exportTitle }} + PRODUCTION-GRADE + + +
+ Real components, real tokens — nothing inlined, nothing to translate. +
+ +
+ ! + Badge isn't mapped to your library — export falls back to the Veect default for it. + +
+
+
+
+ +
+ {{ item.name }} + + {{ item.note }} +
+
+
+
+ Ready to ship. +
npm i && npm run dev
+
+ · {{ exportImportsLine }} + · tokens as CSS variables — tokens.css + · deterministic — same tree, same code +
+ +
+ + +
+
+
+
+
+
+ + +
+
+
+ + +
+
+ + + +
+
⏎ run · ↑↓ navigate · esc close · ✦ routes to compose
+
+
+
+ +
+ +
+ {{ item.glyph }} + {{ item.text }} + + + + +
+
+
+ +
+
+ + + diff --git a/docs/product/reference/project/Weave - warm dark (backup).dc.html b/docs/product/reference/project/Weave - warm dark (backup).dc.html new file mode 100644 index 0000000..daeeb67 --- /dev/null +++ b/docs/product/reference/project/Weave - warm dark (backup).dc.html @@ -0,0 +1,3730 @@ + + + + + + + + + + + + + + + + +
+ + +
+
+ + +
+ + Weave + design-system-native canvas +
+

Bring your design system to life.

+

Weave turns your own tokens and components into real, production-ready React — on-brand, high-craft, nothing lost in translation.

+
+ + +
+
+ +
+
+ + + +

Paste your tokens

+

Tailwind config or CSS variables. Weave parses, contrast-checks, and re-themes the base kit — no hard stops.

+
+ +
+
+ PARSE + +
+ {{ item.glyph }} + {{ item.label }} + + {{ item.note }} +
+
+
+ +
+ CONTRAST — TOKEN PAIRS + +
+ {{ item.pair }} + + {{ item.ratio }} + {{ item.badge }} +
+
+
+
+ +
+
+
+ + + +

Start with five tokens

+

The essentials, set over the base kit. Everything stays a token — swap in the real file any time.

+
+
+
+ BRAND COLOR +
+ + + +
+
+
+ NEUTRALS +
+ + + +
+
+
+ CORNER RADIUS +
+ + {{ radiusLabel }} +
+
+
+ TYPE +
+ + + +
+
+
+ SPACING SCALE +
+ + + +
+
+ +
+
+
NEW
+
+ Email +
you@company.com
+
+
+
Get started
+
Docs
+
+ live preview — your tokens over the base kit +
+
+
+ + +
+ +

Here's the base kit — in Weave gray.

+
+ +

Your system is live.

+
+ {{ revealStat }} +
+
+
+
Get started
+
Secondary
+
+
+
NEW
+
Filter · 3
+
+
+ Email +
you@company.com
+
+
+
+
MK
+
+ Maya Kade + Product designer +
+
+
+
+
+
+ Overview + Members + Billing +
+
+
+
+
+
+
+ Notifications +
+
+
+
+
+
+ volume — 62 +
+
+ Ag + {{ revealFontNote }} +
+
+ +
+ + ⌘K anywhere · ✦ to compose +
+
+
+ +
+
+
+ + +
+ +
+ +
+ + +
+
+ +
+ Solstice — Marketing site + solstice system · autosaved +
+
+
+ + +
+
+ + + +
+
+
+
+
+ + + + {{ saveLabel }} + + +
+ + + +
+
+ + + + + + + + + + + +
+ +
+ +
+ + +
+
+ + + +
+
+ + +
+ +
+
+ +
+ {{ leftTabTitle }} +
+
+ + + + + + + + +
BASE KIT — MAPPED TO YOUR CODE
+ +
+ {{ item.glyph }} + {{ item.name }} + + + +
+
+
PATTERNS — YOURS
+ + +
+ + {{ item.name }} + +
+
+
+ +
Save a selection as a pattern — it lives here, in your system.
+
+
CUSTOM — YOUR CODE
+ +
+ +
+ {{ item.name }} + {{ item.file }} +
+ + +
+
+ + +
+ + +
COLOR — BRAND
+ + +
+
+
+ + + +
+
+ + +
+
+ edits ripple across canvas + code + +
+
+
+
+
+ + + +
+ {{ whereUsed }} +
COLOR — NEUTRAL
+
+ + + +
+
TYPE
+
+ Ag + +
+
NEUTRALS
+
+ + + +
+
RADIUS
+
+ radius-md + + + {{ radiusChip }} + +
+
SPACING
+
+ + + +
+ 4 · 8 · 12 · 16 · 24 · 32 · 48 +
+ +
HISTORY — EVERY CHANGE, LABELLED
+ +
No steps yet — every edit lands here with a label. Click one to travel back.
+
+ + + +
+ +
+
+
+ +
+
+ {{ canvasEl }} +
+ +
+
+ + + + +
+
+ ISOLATION + {{ isoName }} + {{ isoMap }} + + real runtime · variants × states + +
+
{{ isoEl }}
+
+
+ +
+ {{ bubbleTag }} + + + +
+
+
+
+ + + +
+ + + + + scroll — pan · ⌘ scroll — zoom · drag board — pan + +
+ +
+
+ + + + + +
+
+ +
+
+
+ +
+ + COMPOSE — CONSTRAINED TO YOUR SYSTEM + + + +
+
+ +
+ +
+ + Describe a section — Weave composes it from your mapped components and tokens only. +
+ + + +
+ ◎ pick — scope an edit to one element
✎ draw — scribble a note on the board
+
+
+ +
+
+ {{ item.roleLabel }} + +
+ + ▧ {{ att.name }} + +
+
+ {{ item.text }} + +
+ + + staged as a ghost — nothing applied +
+
+ +
+ + +
+
+
+
+
+ +
+ + composing from your system… +
+
+
+
+
+ +
+ + + +
+
+ +
+ + + +
+
+
+ + + + + + +
+ + +
+ + + + ▧ {{ att.name }} + + + ⏎ send · esc dismiss + +
+
+
+
+
+
+ + +
+ +
+
+ +
+ {{ rightTabTitle }} +
+
+ + + +
+ Select a component on the canvas. +
click — select · drag — move
⌘K — commands · ✦ — compose
⌘Z — undo · ⌫ — delete
+
+
+ +
+
+ {{ selGlyph }} +
+ {{ selName }} + {{ selType }} +
+
+ + +
+ FRAME + +
+ width +
+ + {{ frameWVal }} + +
+
+
+
+ +
+ CONTENT + +
+
+ +
+ VARIANT +
+ + + +
+
+
+ +
+ SIZE +
+ + + +
+ WEIGHT +
+ + + +
+
+
+ +
+ TONE +
+ + + +
+ {{ toneNote }} +
+
+ +
+ LAYOUT +
+ + + +
+
+
+ gap +
+ + {{ selGap }} + +
+
+
+ padding +
+ + {{ selPad }} + +
+
+
+
+
+
+ + + +
+ {{ item.cat }} + +
+
+ +
+
+
+ TOKENS BOUND + +
+ {{ item.prop }} + + {{ item.tok }} +
+
+
+
+ + + + + +
+
+
+
+ + +
+ +
+ + Critique this screen against your craft standard. + Spacing, hierarchy, contrast, alignment, states — every fix lands in your tokens and components, with the why. + + standard: Impeccable +
+
+ +
+
+ Scanning spacing, hierarchy, contrast… +
+
+ +
+ {{ polishCount }} + + + +
+ +
+
+ {{ item.sev }} + {{ item.title }} +
+ {{ item.why }} + +
+
+
+ +
+ + Impeccable. Nothing to fix. + Every check passed against your standard. + +
+
+
+
+ +
+
+
+ + +
+
+
+ + + + + + + + + + +
+ +
+
{{ codeDraftEl }}
+ +
+
+ +
{{ codeEl }}
+
+
+ prettier ✓tsc --noEmit ✓imports @solstice/ui +
+
+
+ +
+
+
+ + +
+
+
+ + Weave +
+

Sign in.

+

Your system is waiting — everything you compose exports as real React.

+ + +
+ + or + +
+ + + design-partner MVP — accounts are provisioned manually +
+
+
+ + +
+
+ + Weave + + + + + +
+
+
+
+

Projects

+ {{ homeMeta }} +
+
+ + + + +
+

Design systems

+
+
+
+ + + +
+
+
+ Solstice + {{ homeSysMeta }} +
+ + +
+
+ +
+
+
+
+
+ + +
+
+ + Settings + + +
+
+
+
+ DESIGN SYSTEM +
+ +
+ Solstice + {{ setSysMeta }} +
+ +
+ contrast — 3 pairs checked · brand body text passes AA at brand-700 +
+
+ COMPONENT MAPPINGS — THE MERGE BAR +
+ +
+ {{ item.glyph }} + {{ item.name }} + {{ item.path }} + +
+
+
+ unmapped components fall back to the Weave base kit at export — the warning follows them +
+
+ CRAFT STANDARD — POLISH RULESET +
+ + + +
+
+
+ EXPORT DEFAULTS +
+ +
+ {{ item.label }} + {{ item.note }} + {{ item.chip }} +
+
+
+
+
+ PLAN — DESIGN PARTNER +
+
+ $0 during the MVP + billing starts at GA · seats 2 of 5 used · no card on file +
+ +
+
+ +
+
+
+
+ + +
+
+ + Profile + +
+
+
+
+ M +
+ Maya Kade + maya@solstice.team · Product designer +
+ DESIGN PARTNER +
+
+ PREFERENCES +
+ App theme +
+ + + +
+
+
+
+ WORKSPACE — SOLSTICE +
+ +
+ {{ item.initials }} +
+ {{ item.name }} + {{ item.role }} +
+ {{ item.chip }} +
+
+ +
+
+
+ KEYBOARD +
+ ⌘K — commands · ✦ — compose · ⌘Z — undo + ⌘0 — zoom to fit · ⌘1 — 100% · scroll — pan · ⌘scroll — zoom + ⌫ — delete selection · esc — dismiss anything +
+
+ +
+
+
+
+ + +
+
+ + System update + re-import tokens · diff · apply with undo + +
+
+
+ +

Devon updated the tokens. Paste the new file — Weave diffs it against the live system and shows the impact before anything changes.

+ + +
+ +
+ +
+ {{ item.tag }} + {{ item.tok }} + + {{ item.from }} + + + {{ item.to }} +
+
+
+
+ i + {{ sysImpact }} +
+
+ + +
+
+ +
+ + System updated. + radius-md 12 → 10 rippled across every bound component on the canvas and in the code. Bindings preserved — nothing broke silently. +
+ + +
+
+
+
+
+
+
+ + +
+
{{ previewEl }}
+
+ + preview — real components, real states + + +
+
+
+ + +
+
+
+
+ {{ exportTitle }} + PRODUCTION-GRADE + + +
+ Real components, real tokens — nothing inlined, nothing to translate. +
+ +
+ ! + Badge isn't mapped to your library — export falls back to the Weave default for it. + +
+
+
+
+ +
+ {{ item.name }} + + {{ item.note }} +
+
+
+
+ Ready to ship. +
npm i && npm run dev
+
+ · {{ exportImportsLine }} + · tokens as CSS variables — tokens.css + · deterministic — same tree, same code +
+ +
+ + +
+
+
+
+
+
+ + +
+
+
+ + +
+
+ + + +
+
⏎ run · ↑↓ navigate · esc close · ✦ routes to compose
+
+
+
+ +
+ +
+ {{ item.glyph }} + {{ item.text }} + + + + +
+
+
+ +
+
+ + + diff --git a/docs/product/reference/project/support.js b/docs/product/reference/project/support.js new file mode 100644 index 0000000..2a774eb --- /dev/null +++ b/docs/product/reference/project/support.js @@ -0,0 +1,1702 @@ +// GENERATED from dc-runtime/src/*.ts — do not edit. Rebuild with `cd dc-runtime && bun run build`. +"use strict"; +(() => { + var __defProp = Object.defineProperty; + var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); + + // src/react.ts + function getReact() { + const R = window.React; + if (!R) throw new Error("dc-runtime: window.React is not available yet"); + return R; + } + function getReactDOM() { + const RD = window.ReactDOM; + if (!RD) throw new Error("dc-runtime: window.ReactDOM is not available yet"); + return RD; + } + var h = ((...args) => getReact().createElement( + ...args + )); + + // src/parse.ts + function parseDcDocument(doc) { + const dc = doc.querySelector("x-dc"); + if (!dc) return null; + const scriptEl = doc.querySelector("script[data-dc-script]"); + const { props, preview } = parseDataProps( + scriptEl?.getAttribute("data-props") ?? null + ); + return { + template: dc.innerHTML, + js: scriptEl ? scriptEl.textContent || "" : "", + props, + preview + }; + } + function parseDcText(src) { + const openMatch = /]*)?>/.exec(src); + if (!openMatch) return null; + const close = src.lastIndexOf(""); + if (close === -1 || close < openMatch.index) return null; + const template = src.slice(openMatch.index + openMatch[0].length, close); + const doc = new DOMParser().parseFromString(src, "text/html"); + const scriptEl = doc.querySelector("script[data-dc-script]"); + const { props, preview } = parseDataProps( + scriptEl?.getAttribute("data-props") ?? null + ); + return { + template, + js: scriptEl ? scriptEl.textContent || "" : "", + props, + preview + }; + } + function parseDataProps(raw) { + if (!raw) return { props: null, preview: null }; + let parsed; + try { + parsed = JSON.parse(raw); + } catch { + return { props: null, preview: null }; + } + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + return { props: null, preview: null }; + } + const obj = parsed; + const preview = obj.$preview && typeof obj.$preview === "object" ? obj.$preview : null; + const rest = {}; + for (const k of Object.keys(obj)) { + if (k[0] !== "$") rest[k] = obj[k]; + } + return { props: Object.keys(rest).length ? rest : null, preview }; + } + function dcNameFromPath(pathname) { + let p = pathname || ""; + try { + p = decodeURIComponent(p); + } catch { + } + const base = p.split("/").pop() || "Root"; + return base.replace(/\.dc\.html$/, "").replace(/\.html?$/, "") || "Root"; + } + + // src/boot.ts + var BASE_CSS = ` + .sc-placeholder{background:color-mix(in srgb,currentColor 8%,transparent); + border:1px solid color-mix(in srgb,currentColor 50%,transparent); + border-radius:2px;box-sizing:border-box;overflow:hidden} + @keyframes sc-shine{0%{background-position:100% 50%}100%{background-position:0% 50%}} + html.sc-dc-streaming .sc-placeholder, + html.sc-dc-streaming .sc-interp.sc-missing{position:relative; + background:color-mix(in srgb,currentColor 5%,transparent); + border-color:transparent} + html.sc-dc-streaming .sc-placeholder::before, + html.sc-dc-streaming .sc-interp.sc-missing::before{content:''; + position:absolute;inset:0;pointer-events:none; + background:linear-gradient(90deg,rgba(217,119,87,0) 25%,rgba(247,225,211,.95) 37%,rgba(217,119,87,0) 63%); + background-size:400% 100%;animation:sc-shine 1.4s ease infinite} + html.sc-dc-streaming .sc-placeholder:nth-child(n+9 of .sc-placeholder)::before, + html.sc-dc-streaming .sc-interp.sc-missing:nth-child(n+9 of .sc-interp.sc-missing)::before{animation:none; + background:color-mix(in srgb,currentColor 8%,transparent)} + .sc-placeholder-error{padding:4px 8px;font:11px/1.4 ui-monospace,monospace; + color:color-mix(in srgb,currentColor 70%,transparent);word-break:break-word} + .sc-interp.sc-missing{display:inline-block;width:2em;height:1em;overflow:hidden; + vertical-align:text-bottom;background:rgba(255,255,255,.3);border:1px solid rgba(0,0,0,.5); + border-radius:2px;box-sizing:border-box;color:transparent; + user-select:none} + .sc-interp.sc-unresolved{font-family:ui-monospace,monospace;font-size:.85em; + color:color-mix(in srgb,currentColor 50%,transparent); + background:color-mix(in srgb,currentColor 10%,transparent);border-radius:3px; + padding:0 3px} + .sc-host.sc-has-error{position:relative} + .sc-logic-error{position:absolute;top:8px;left:8px;z-index:2147483647;max-width:60ch; + padding:6px 10px;background:#b00020;color:#fff;font:12px/1.4 ui-monospace,monospace; + border-radius:4px;white-space:pre-wrap;pointer-events:none} + /* Mirrors PRINT_BASELINE_CSS in apps/web deck-stage-export.ts \u2014 keep both + in sync until dc-runtime regains a build step. */ + @media print { + @page { margin: 0.5cm; } + figure, table { break-inside: avoid; } + #dc-root, #dc-root > .sc-host { height: auto; } + *, *::before, *::after { + print-color-adjust: exact; -webkit-print-color-adjust: exact; + backdrop-filter: none !important; -webkit-backdrop-filter: none !important; + animation-delay: -99s !important; animation-duration: .001s !important; + animation-iteration-count: 1 !important; animation-fill-mode: both !important; + animation-play-state: running !important; transition-duration: 0s !important; + } + } + `; + var FULL_PAGE_CSS = "html,body{height:100%;margin:0}#dc-root,#dc-root>.sc-host{height:100%}"; + function rootNameForDocument(doc, loc) { + let bootPath = loc.pathname || ""; + if (!/\.dc\.html?$/i.test(safeDecode(bootPath))) { + try { + bootPath = new URL(doc.baseURI || "/").pathname; + } catch { + } + } + return dcNameFromPath(bootPath); + } + function safeDecode(s) { + try { + return decodeURIComponent(s); + } catch { + return s; + } + } + function boot(runtime, doc = document) { + const parsed = parseDcDocument(doc); + if (!parsed) return null; + const React = getReact(); + const rootName = rootNameForDocument(doc, location); + runtime.markFetched(rootName); + runtime.setRootName(rootName); + runtime.adoptParsed(rootName, parsed); + if (!window.__resources) { + fetch(location.href).then((res) => res.ok ? res.text() : "").then((t) => { + const raw = t ? parseDcText(t) : null; + if (raw?.template) runtime.updateHtml(rootName, raw.template); + }).catch(() => { + }); + } + const dc = doc.querySelector("x-dc"); + const hostEl = doc.createElement("div"); + hostEl.id = "dc-root"; + dc.replaceWith(hostEl); + if (!parsed.preview) { + const s = doc.createElement("style"); + s.textContent = FULL_PAGE_CSS; + doc.head.appendChild(s); + } + const Root = runtime.getDC(rootName); + const entry = runtime.registry.get(rootName); + function StandaloneRoot() { + const [, setTick] = React.useState(0); + React.useEffect(() => { + const sub = () => setTick((n) => n + 1); + entry.subs.add(sub); + return () => { + entry.subs.delete(sub); + }; + }, []); + const defaults = React.useMemo(() => { + const d = {}; + for (const k in entry.propsMeta || {}) { + const v = entry.propsMeta?.[k]?.default; + if (v !== void 0) d[k] = v; + } + return d; + }, [entry.propsMeta]); + return h(Root, { ...defaults, ...entry.propOverrides || {} }); + } + const ReactDOM = getReactDOM(); + if (ReactDOM.createRoot) + ReactDOM.createRoot(hostEl).render(h(StandaloneRoot)); + else ReactDOM.render(h(StandaloneRoot), hostEl); + return rootName; + } + + // src/expr.ts + var IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*/; + var NUMBER_RE = /^-?\d+(\.\d+)?$/; + function resolve(vals, src) { + const expr = String(src).trim(); + if (!expr) return void 0; + if (expr[0] === "(" && expr[expr.length - 1] === ")" && parensWrapWhole(expr)) { + return resolve(vals, expr.slice(1, -1)); + } + const eq = findTopLevelEquality(expr); + if (eq) { + const lv = resolve(vals, expr.slice(0, eq.index)); + const rv = resolve(vals, expr.slice(eq.index + eq.op.length)); + switch (eq.op) { + case "===": + return lv === rv; + case "!==": + return lv !== rv; + case "==": + return lv == rv; + default: + return lv != rv; + } + } + if (expr[0] === "!") return !resolve(vals, expr.slice(1)); + if (expr === "true") return true; + if (expr === "false") return false; + if (expr === "null") return null; + if (expr === "undefined") return void 0; + if (NUMBER_RE.test(expr)) return Number(expr); + if (expr.length >= 2 && (expr[0] === '"' || expr[0] === "'") && expr[expr.length - 1] === expr[0]) { + return expr.slice(1, -1); + } + return resolvePath(vals, expr); + } + function parensWrapWhole(expr) { + let depth = 0; + for (let i = 0; i < expr.length - 1; i++) { + if (expr[i] === "(") depth++; + else if (expr[i] === ")") { + depth--; + if (depth === 0) return false; + } + } + return true; + } + function findTopLevelEquality(expr) { + let depth = 0; + for (let i = 0; i < expr.length; i++) { + const c = expr[i]; + if (c === "[" || c === "(") depth++; + else if (c === "]" || c === ")") depth--; + else if (depth === 0 && (c === "=" || c === "!") && expr[i + 1] === "=") { + if (i > 0 && (expr[i - 1] === "=" || expr[i - 1] === "!")) continue; + if (!expr.slice(0, i).trim()) continue; + const op = expr[i + 2] === "=" ? c + "==" : c + "="; + return { index: i, op }; + } + } + return null; + } + function resolvePath(vals, expr) { + const head = expr.match(IDENT_RE); + if (!head) return void 0; + let cur = vals == null ? void 0 : vals[head[0]]; + let i = head[0].length; + while (i < expr.length) { + if (expr[i] === ".") { + const m = expr.slice(i + 1).match(IDENT_RE) || expr.slice(i + 1).match(/^\d+/); + if (!m) return void 0; + cur = cur == null ? void 0 : cur[m[0]]; + i += 1 + m[0].length; + } else if (expr[i] === "[") { + let depth = 1; + let j = i + 1; + while (j < expr.length && depth > 0) { + if (expr[j] === "[") depth++; + else if (expr[j] === "]") { + depth--; + if (depth === 0) break; + } + j++; + } + if (depth !== 0) return void 0; + const key = resolve(vals, expr.slice(i + 1, j)); + cur = cur == null ? void 0 : cur[key]; + i = j + 1; + } else { + return void 0; + } + } + return cur; + } + + // src/encode.ts + var CAMEL_ATTR = "sc-camel-"; + var INLINE_TEXT_TAGS = new Set( + "a abbr b bdi bdo br cite code del dfn em i ins kbd mark q s samp small span strike strong sub sup u var wbr".split( + " " + ) + ); + var RAW_WRAP = { + select: "sc-raw-select", + table: "sc-raw-table", + tbody: "sc-raw-tbody", + thead: "sc-raw-thead", + tfoot: "sc-raw-tfoot", + tr: "sc-raw-tr", + td: "sc-raw-td", + th: "sc-raw-th", + caption: "sc-raw-caption" + }; + var RAW_UNWRAP = Object.fromEntries( + Object.entries(RAW_WRAP).map(([k, v]) => [v, k]) + ); + var EVENT_MAP = { + onclick: "onClick", + onchange: "onChange", + oninput: "onInput", + onsubmit: "onSubmit", + onkeydown: "onKeyDown", + onkeyup: "onKeyUp", + onkeypress: "onKeyPress", + onmousedown: "onMouseDown", + onmouseup: "onMouseUp", + onmouseenter: "onMouseEnter", + onmouseleave: "onMouseLeave", + onfocus: "onFocus", + onblur: "onBlur", + ondoubleclick: "onDoubleClick", + oncontextmenu: "onContextMenu" + }; + var ATTRS = `(?:[^>"']|"[^"]*"|'[^']*')*`; + var IMPORT_SELF_CLOSE_RE = new RegExp( + "<(x-import|dc-import)(" + ATTRS + ")/>", + "gi" + ); + var CAMEL_ATTR_RE = /(\s)([a-z]+[A-Z][A-Za-z0-9]*)(\s*=)/g; + function encodeCase(html) { + html = html.replace( + IMPORT_SELF_CLOSE_RE, + (_, t, a) => "<" + t + a + ">" + ); + html = html.replace(/)/gi, "/gi, ""); + html = html.replace( + CAMEL_ATTR_RE, + (_, sp, name, eq) => sp + CAMEL_ATTR + name.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase()) + eq + ); + for (const [real, alias] of Object.entries(RAW_WRAP)) { + html = html.replace( + new RegExp("(])", "gi"), + "$1" + alias + ); + } + return html; + } + function kebabToCamel(s) { + return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); + } + function cssToObj(css) { + const o = {}; + for (const decl of css.split(";")) { + const i = decl.indexOf(":"); + if (i < 0) continue; + const prop = decl.slice(0, i).trim(); + o[prop.startsWith("--") ? prop : kebabToCamel(prop)] = decl.slice(i + 1).trim(); + } + return o; + } + function compileAttr(raw) { + const whole = raw.match(/^\s*\{\{([\s\S]+?)\}\}\s*$/); + if (whole) { + const path = whole[1]; + return (vals) => resolve(vals, path); + } + if (raw.includes("{{")) { + const parts = raw.split(/\{\{([\s\S]+?)\}\}/g); + return (vals) => parts.map((s, i) => i & 1 ? resolve(vals, s) ?? "" : s).join(""); + } + return () => raw; + } + + // src/compile.ts + function collectProps(node, kind, host) { + const propGetters = []; + const pseudoClasses = []; + let hintSize = null; + for (const { name, value } of [...node.attributes]) { + if (name === "sc-name" || name === "data-dc-tpl") continue; + let key = name; + if (key.startsWith(CAMEL_ATTR)) + key = kebabToCamel(key.slice(CAMEL_ATTR.length)); + if (key === "hint-size") { + hintSize = value; + continue; + } + if (key.startsWith("style-")) { + pseudoClasses.push(host.pseudoClass(key.slice(6), value)); + continue; + } + if (kind !== "dom") { + if (key.includes("-") && !(kind === "x-import" && (key.startsWith("aria-") || key.startsWith("data-")))) + key = kebabToCamel(key); + } else { + if (key === "class") key = "className"; + else if (key === "for") key = "htmlFor"; + else if (key.startsWith("on")) + key = EVENT_MAP[key] || "on" + key[2].toUpperCase() + key.slice(3); + } + propGetters.push([key, compileAttr(value)]); + } + return { propGetters, pseudoClasses, hintSize }; + } + var HOST_STYLE_PROPS = /* @__PURE__ */ new Set([ + "position", + "left", + "right", + "top", + "bottom", + "inset", + "width", + "height", + "z-index", + "transform" + ]); + function hostPositionStyle(style) { + const all = typeof style === "string" ? cssToObj(style) : style != null && typeof style === "object" ? style : null; + if (!all) return void 0; + const out = {}; + for (const [k, v] of Object.entries(all)) { + const kebab = k.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase()); + if (HOST_STYLE_PROPS.has(kebab)) out[k] = v; + } + return Object.keys(out).length ? out : void 0; + } + function compileTemplate(html, host) { + const tpl = document.createElement("template"); + //! nosemgrep: direct-inner-html-assignment + tpl.innerHTML = encodeCase(html); + let tplN = 0; + (function stamp(node) { + if (node.nodeType === Node.ELEMENT_NODE) { + node.setAttribute("data-dc-tpl", String(tplN++)); + } + for (const c of node.childNodes) stamp(c); + })(tpl.content); + const builders = walkChildren(tpl.content, host); + const render = ((vals, ctx) => builders.map((b, i) => b(vals || {}, ctx, i))); + render.__annotated = tpl.innerHTML; + return render; + } + function walkChildren(node, host) { + return [...node.childNodes].map((c) => walk(c, host)).filter((b) => b != null); + } + function walk(node, host) { + if (node.nodeType === Node.TEXT_NODE) return walkText(node); + if (node.nodeType !== Node.ELEMENT_NODE) return null; + const el = node; + const tag = el.tagName.toLowerCase(); + if (tag === "sc-for") return walkFor(el, host); + if (tag === "sc-if") return walkIf(el, host); + if (tag === "x-import") return walkXImport(el, host); + if (tag === "sc-helmet") return host.helmet(el); + if (tag === "dc-import") return walkComponent(el, host); + return walkElement(el, host); + } + var warnedHoles = /* @__PURE__ */ new Set(); + function warnUnresolved(ctx, what) { + const key = (ctx?.__name || "?") + "\0" + what; + if (warnedHoles.has(key)) return; + warnedHoles.add(key); + console.warn("[dc-runtime] " + (ctx?.__name || "template") + ": " + what); + } + function walkText(node) { + const txt = node.nodeValue ?? ""; + if (!txt.includes("{{")) { + if (!txt.trim() && !txt.includes(" ")) return null; + return () => txt; + } + const parts = txt.split(/\{\{([\s\S]+?)\}\}/g); + return (vals, ctx, key) => h( + getReact().Fragment, + { key }, + ...parts.map((p, i) => { + if (!(i & 1)) return p; + const v = resolve(vals, p); + if (v === void 0) { + if (!ctx?.__streamingNow) { + if (document.body?.hasAttribute("data-dc-editor-on")) { + return h( + "span", + { key: i, className: "sc-interp sc-unresolved" }, + "{{ " + p.trim() + " }}" + ); + } + warnUnresolved( + ctx, + "{{ " + p.trim() + " }} never resolved \u2014 rendered as empty" + ); + return null; + } + return h( + "span", + { key: i, className: "sc-interp sc-missing" }, + p.trim() + ); + } + if (getReact().isValidElement(v) || Array.isArray(v)) { + return h(getReact().Fragment, { key: i }, v); + } + if (v === null || typeof v === "boolean") return null; + return h("span", { key: i, className: "sc-interp" }, String(v)); + }) + ); + } + function walkFor(el, host) { + const listGet = compileAttr(el.getAttribute("list") || ""); + const asName = el.getAttribute("as") || "item"; + const hintN = parseInt(el.getAttribute("hint-placeholder-count") || "0", 10); + const kids = walkChildren(el, host); + const listSrc = el.getAttribute("list") || ""; + return (vals, ctx, key) => { + let list = listGet(vals); + if (!Array.isArray(list)) { + if (!ctx?.__streamingNow) { + if (list !== void 0 && list !== null) { + warnUnresolved( + ctx, + 'sc-for list="' + listSrc + '" is not an array (' + typeof list + ")" + ); + } + list = []; + } else { + list = hintN > 0 ? Array(hintN).fill(void 0) : []; + } + } + return h( + getReact().Fragment, + { key }, + list.map((item, i) => { + const sub = { ...vals, [asName]: item, $index: i }; + return h( + getReact().Fragment, + { key: i }, + kids.map((b, j) => b(sub, ctx, j)) + ); + }) + ); + }; + } + function walkIf(el, host) { + const valGet = compileAttr(el.getAttribute("value") || ""); + const hintRaw = el.getAttribute("hint-placeholder-val"); + const hintGet = hintRaw != null ? compileAttr(hintRaw) : null; + const kids = walkChildren(el, host); + return (vals, ctx, key) => { + let v = valGet(vals); + if (v === void 0 && hintGet && ctx?.__streamingNow) v = hintGet(vals); + return v ? h( + getReact().Fragment, + { key }, + kids.map((b, j) => b(vals, ctx, j)) + ) : null; + }; + } + function walkComponent(el, host) { + const name = el.getAttribute("name") || el.getAttribute("component") || ""; + el.removeAttribute("name"); + el.removeAttribute("component"); + const tplId = el.getAttribute("data-dc-tpl"); + const styleRaw = el.getAttribute("style"); + el.removeAttribute("style"); + const styleGet = styleRaw != null ? compileAttr(styleRaw) : null; + const { propGetters, hintSize } = collectProps(el, "dc-import", host); + const kids = walkChildren(el, host); + return (vals, ctx, key) => { + const props = { + key, + __hintSize: hintSize, + __tplId: tplId, + __hostStyle: styleGet ? hostPositionStyle(styleGet(vals)) : void 0 + }; + for (const [k, g] of propGetters) { + const v = g(vals); + if (k === "dcProps") { + if (v && typeof v === "object") Object.assign(props, v); + continue; + } + props[k] = v; + } + if (kids.length) props.children = kids.map((b, j) => b(vals, ctx, j)); + return h(host.component(name), props); + }; + } + function walkXImport(el, host) { + const globalNameGet = compileAttr( + el.getAttribute("component-from-global-scope") || "" + ); + const exportNameGet = compileAttr( + el.getAttribute("component") || el.getAttribute("name") || "" + ); + const fromRaw = el.getAttribute("from") || (el.getAttribute("component-from-global-scope") ? "" : el.getAttribute("src") || el.getAttribute("import") || ""); + const urls = fromRaw.trim() ? fromRaw.trim().split(/\s+/) : []; + const url = urls.length ? urls[urls.length - 1] : ""; + const kindOf = (u) => /\.(jsx|tsx)(\?|#|$)/i.test(u) ? "jsx" : "js"; + const tplId = el.getAttribute("data-dc-tpl"); + const styleRaw = el.getAttribute("style"); + el.removeAttribute("style"); + const styleGet = styleRaw != null ? compileAttr(styleRaw) : null; + const wrap = tplId != null || styleGet != null; + const { propGetters, hintSize } = collectProps(el, "x-import", host); + const hasContent = el.children.length > 0 || !!(el.textContent || "").trim(); + const kids = hasContent ? walkChildren(el, host) : []; + const urlBindable = fromRaw.includes("{{"); + if (urls.length && !urlBindable) { + let prev; + for (const u of urls) prev = host.loadExternal(kindOf(u), u, prev); + } + const evalName = (g, vals) => { + const v = g(vals); + const s = v == null ? "" : String(v); + return s.includes("{{") ? "" : s; + }; + return (vals, ctx, key) => { + const globalName = evalName(globalNameGet, vals); + const name = globalName || evalName(exportNameGet, vals); + const C = !name || urlBindable ? null : globalName ? host.resolveExternalGlobal(url, globalName) : host.resolveExternal(url, name); + const hostStyle = styleGet ? hostPositionStyle(styleGet(vals)) : void 0; + const wrapper = wrap ? { + key, + className: "sc-host-x", + "data-dc-tpl": tplId, + style: hostStyle || { display: "contents" } + } : null; + if (!C) { + const error = urlBindable ? "x-import `from` cannot contain {{ \u2026 }} \u2014 module URLs are resolved at parse time; use a literal URL" : host.resolveExternalError(url, name); + const ph = host.placeholder({ + key: wrapper ? void 0 : key, + name, + hintSize, + error + }); + return wrapper ? h("div", wrapper, ph) : ph; + } + const props = wrapper ? {} : { key }; + let unresolvedHole = false; + for (const [k, g] of propGetters) { + if (k === "component" || k === "componentFromGlobalScope" || k === "from") { + continue; + } + const v = g(vals); + if (v === void 0) unresolvedHole = true; + if (k === "dcProps") { + if (v && typeof v === "object") Object.assign(props, v); + continue; + } + props[k] = v; + } + if (unresolvedHole && ctx?.__htmlStreamingNow) { + const ph = host.placeholder({ + key: wrapper ? void 0 : key, + name, + hintSize, + error: null + }); + return wrapper ? h("div", wrapper, ph) : ph; + } + if (kids.length) props.children = kids.map((b, j) => b(vals, ctx, j)); + return wrapper ? h("div", wrapper, h(C, props)) : h(C, props); + }; + } + function contentKey(el) { + const clone = el.cloneNode(true); + for (const d of clone.querySelectorAll("*")) { + while (d.attributes.length) d.removeAttribute(d.attributes[0].name); + } + const s = clone.innerHTML; + let h2 = 5381; + for (let i = 0; i < s.length; i++) h2 = (h2 << 5) + h2 + s.charCodeAt(i) | 0; + return s.length + "." + (h2 >>> 0).toString(36); + } + var NEVER_CONTENT_KEYED = new Set( + "script style textarea option title select canvas iframe video audio".split( + " " + ) + ); + var NOT_INLINE_SELECTOR = ":not(" + [...INLINE_TEXT_TAGS].join(",") + ")"; + function walkElement(el, host) { + const realTag = RAW_UNWRAP[el.localName] || el.localName; + const tplId = el.getAttribute("data-dc-tpl"); + const inlineOnly = el.childNodes.length > 0 && !NEVER_CONTENT_KEYED.has(realTag) && el.querySelector(NOT_INLINE_SELECTOR) === null; + const keySuffix = inlineOnly ? "|" + contentKey(el) : ""; + const { propGetters, pseudoClasses } = collectProps(el, "dom", host); + const kids = walkChildren(el, host); + return (vals, ctx, key) => { + const props = { + key: key + keySuffix, + "data-dc-tpl": tplId + }; + for (const [k, g] of propGetters) { + let v = g(vals); + if (k === "style" && typeof v === "string") v = cssToObj(v); + if ((k === "value" || k === "checked") && v === void 0) { + v = k === "checked" ? false : ""; + } + props[k] = v; + } + if (pseudoClasses.length) { + props.className = [props.className, ...pseudoClasses].filter(Boolean).join(" "); + } + return h(realTag, props, ...kids.map((b, j) => b(vals, ctx, j))); + }; + } + + // src/logic.ts + var StreamableLogic = class { + constructor(props) { + __publicField(this, "props"); + __publicField(this, "state", {}); + /** Back-pointer to the wrapper component, installed after construction. */ + __publicField(this, "__host"); + this.props = props || {}; + } + setState(update, cb) { + this.__host && this.__host.__setLogicState(update, cb); + } + forceUpdate() { + this.__host && this.__host.forceUpdate(); + } + componentDidMount() { + } + componentDidUpdate(_prevProps) { + } + componentWillUnmount() { + } + /** The flat object the template renders against (merged over props). */ + renderVals() { + return {}; + } + }; + function evalDcLogic(src) { + //! nosemgrep: eval-and-function-constructor + const fn = new Function( + "DCLogic", + "StreamableLogic", + "React", + src + '\n;return (typeof Component!=="undefined"&&Component)||undefined;' + ); + return fn(StreamableLogic, StreamableLogic, getReact()); + } + + // src/component.ts + function shallowEqual(a, b) { + if (!b) return false; + const ak = Object.keys(a).filter((k) => k !== "children"); + const bk = Object.keys(b).filter((k) => k !== "children"); + if (ak.length !== bk.length) return false; + for (const k of ak) if (a[k] !== b[k]) return false; + return true; + } + function Placeholder({ + name, + hintSize, + streaming, + error + }) { + const [w, hgt] = (hintSize || "100%,60px").split(","); + return h( + "div", + { + className: "sc-placeholder" + (streaming ? " sc-streaming" : ""), + style: { width: w.trim(), height: hgt && hgt.trim() }, + title: name + }, + error ? h( + "div", + { className: "sc-placeholder-error" }, + (name ? name + ": " : "") + error + ) : null + ); + } + function hintToMin(hint) { + if (!hint) return void 0; + const [w, hgt] = hint.split(","); + return { minWidth: w.trim(), minHeight: hgt && hgt.trim() }; + } + function createComponentFactory(registry, ensureFetched) { + const React = getReact(); + const AncestorContext = React.createContext([]); + class StreamableComponent extends React.Component { + constructor(props) { + super(props); + __publicField(this, "__name"); + __publicField(this, "__sub"); + __publicField(this, "__needsDidMount", false); + /** Snapshot of the registry's streaming flags taken at render time — + * builders read it off the RenderCtx (this) to pick placeholder vs + * render-nothing for unresolved values. */ + __publicField(this, "__streamingNow", false); + __publicField(this, "__htmlStreamingNow", false); + /** When a construct throws, remember the (class, registry.ver, props) + * triple so render-time reconcile doesn't re-attempt it on every parent + * re-render. A registry bump (new class, template, external module + * resolving via bumpAll) changes `ver` and breaks the memo so an + * env-dependent constructor can self-heal. */ + __publicField(this, "__failedLogic", null); + __publicField(this, "__failedUserProps", null); + __publicField(this, "__failedVer", -1); + /** Per-instance constructor error — kept here (not on the registry entry) + * so one instance's successful construct can't hide a sibling's failure, + * and a construct can never wipe an eval error `updateJs` recorded on + * `r.logicError`. */ + __publicField(this, "__ctorError", null); + __publicField(this, "logic"); + this.__name = props.__name; + this.state = { __v: 0, __err: null }; + this.__sub = () => { + if (this.state.__err) this.setState({ __err: null }); + this.forceUpdate(); + }; + this.__makeLogic(registry.get(this.__name).Logic, null); + ensureFetched(this.__name); + } + /** Error-boundary hook: a render crash anywhere in this DC's subtree + * (its own template, an x-import'd component, a child DC without its + * own deeper boundary) lands here instead of unmounting the page. */ + static getDerivedStateFromError(e) { + return { __err: e instanceof Error && e.message ? e.message : String(e) }; + } + componentDidCatch(e, info) { + console.error( + "[dc-runtime] render error in <" + this.__name + ">:", + e, + info?.componentStack || "" + ); + } + /** Instantiate the logic class (or the no-op base) and adopt `prevState` + * over its initial state — used both at mount and on hot-swap. */ + __makeLogic(Logic, prevState) { + const L = Logic || StreamableLogic; + try { + this.logic = new L(this.__userProps()); + this.__failedLogic = null; + this.__failedUserProps = null; + this.__ctorError = null; + } catch (e) { + console.error(e); + this.__failedLogic = Logic; + this.__failedUserProps = this.__userProps(); + this.__failedVer = registry.get(this.__name).ver; + this.__ctorError = this.__name + ": " + (e instanceof Error && e.message ? e.message : String(e)); + this.logic = new StreamableLogic( + this.__userProps() + ); + } + this.logic.__host = this; + if (prevState) + this.logic.state = { ...this.logic.state || {}, ...prevState }; + } + /** The props the author's logic + template see — internal __-prefixed + * wiring stripped. */ + __userProps() { + const { __name, __hintSize, __tplId, __hostStyle, ...rest } = this.props; + return rest; + } + __setLogicState(update, cb) { + const prev = this.logic.state; + const patch = typeof update === "function" ? update(prev) : update; + this.logic.state = { ...prev, ...patch }; + this.setState((s) => ({ __v: s.__v + 1 }), cb); + } + /** Swap the logic instance when the registry's Logic class changed + * (streaming completion, hot reload). State carries over; didMount + * re-fires after the swap commits so refs exist. */ + __reconcileLogic() { + const r = registry.get(this.__name); + const Next = r.Logic; + const Cur = this.logic.constructor; + if (Next === Cur || !Next && Cur === StreamableLogic || Next === this.__failedLogic && r.ver === this.__failedVer && shallowEqual(this.__userProps(), this.__failedUserProps)) { + return; + } + if (!this.__needsDidMount) { + try { + this.logic.componentWillUnmount(); + } catch (e) { + console.error(e); + } + } + this.__makeLogic(Next, this.logic.state); + this.__needsDidMount = true; + } + componentDidMount() { + registry.get(this.__name).subs.add(this.__sub); + try { + this.logic.componentDidMount(); + } catch (e) { + console.error(e); + } + } + componentDidUpdate(prevProps) { + this.logic.props = this.__userProps(); + if (this.__needsDidMount) { + if (this.state.__err || !registry.get(this.__name).tpl) return; + this.__needsDidMount = false; + try { + this.logic.componentDidMount(); + } catch (e) { + console.error(e); + } + } else { + try { + this.logic.componentDidUpdate(prevProps); + } catch (e) { + console.error(e); + } + } + } + componentWillUnmount() { + registry.get(this.__name).subs.delete(this.__sub); + if (!this.__needsDidMount) { + try { + this.logic.componentWillUnmount(); + } catch (e) { + console.error(e); + } + } + } + render() { + const r = registry.get(this.__name); + const cls = "sc-host" + (r.htmlStreaming ? " sc-streaming-html" : "") + (r.jsStreaming ? " sc-streaming-js" : ""); + const hintStyle = r.htmlStreaming ? hintToMin(this.props.__hintSize) : void 0; + const hostStyle = this.props.__hostStyle || hintStyle ? { ...hintStyle || {}, ...this.props.__hostStyle || {} } : void 0; + const hostBase = { + className: cls, + style: hostStyle, + "data-sc-name": this.__name, + "data-dc-tpl": this.props.__tplId + }; + const chain = Array.isArray(this.context) ? this.context : []; + if (chain.includes(this.__name)) { + const cycle = [ + ...chain.slice(chain.indexOf(this.__name)), + this.__name + ].join(" \u2192 "); + return h( + "div", + { ...hostBase, className: cls + " sc-has-error" }, + h(Placeholder, { + name: this.__name, + hintSize: this.props.__hintSize, + error: "circular import: " + cycle + }) + ); + } + if (this.state.__err) { + return h( + "div", + { ...hostBase, className: cls + " sc-has-error" }, + h( + "div", + { className: "sc-logic-error", "data-omelette-chrome": "" }, + this.__name + ": " + this.state.__err + ), + h(Placeholder, { + name: this.__name, + hintSize: this.props.__hintSize, + error: this.state.__err + }) + ); + } + this.__reconcileLogic(); + if (!r.tpl) { + return h( + "div", + hostBase, + h(Placeholder, { name: this.__name, hintSize: this.props.__hintSize }) + ); + } + const userProps = this.__userProps(); + this.logic.props = userProps; + let vals = userProps; + let renderErr = r.logicError || this.__ctorError; + try { + vals = { ...userProps, ...this.logic.renderVals() || {} }; + } catch (e) { + console.error(e); + renderErr = this.__name + ".renderVals(): " + (e instanceof Error && e.message ? e.message : String(e)); + } + this.__streamingNow = !!(r.htmlStreaming || r.jsStreaming); + this.__htmlStreamingNow = !!r.htmlStreaming; + return h( + "div", + { ...hostBase, className: cls + (renderErr ? " sc-has-error" : "") }, + renderErr && h( + "div", + { className: "sc-logic-error", "data-omelette-chrome": "" }, + renderErr + ), + h( + AncestorContext.Provider, + { value: [...chain, this.__name] }, + r.tpl(vals, this) + ) + ); + } + } + __publicField(StreamableComponent, "contextType", AncestorContext); + const named = /* @__PURE__ */ new Map(); + function getDC(name) { + const hit = named.get(name); + if (hit) return hit; + function Dispatcher(p) { + const [, setTick] = React.useState(0); + React.useEffect(() => { + const sub = () => setTick((n) => n + 1); + registry.get(name).subs.add(sub); + return () => { + registry.get(name).subs.delete(sub); + }; + }, []); + ensureFetched(name); + return h(StreamableComponent, { ...p, __name: name }); + } + Dispatcher.displayName = name; + named.set(name, Dispatcher); + return Dispatcher; + } + return { + getDC, + StreamableComponent + }; + } + + // src/cdn.ts + var REACT_URL = "https://unpkg.com/react@18.3.1/umd/react.production.min.js"; + var REACT_SRI = "sha384-DGyLxAyjq0f9SPpVevD6IgztCFlnMF6oW/XQGmfe+IsZ8TqEiDrcHkMLKI6fiB/Z"; + var REACT_DOM_URL = "https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"; + var REACT_DOM_SRI = "sha384-gTGxhz21lVGYNMcdJOyq01Edg0jhn/c22nsx0kyqP0TxaV5WVdsSH1fSDUf5YJj1"; + var BABEL_URL = "https://unpkg.com/@babel/standalone@7.29.0/babel.min.js"; + var BABEL_SRI = "sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y"; + function cdnScriptFor(url, sri) { + const res = window.__resources; + const v = res ? res[url] : void 0; + return typeof v === "string" && v ? { src: v } : { src: url, integrity: sri }; + } + + // src/external.ts + var isCustomElementName = (n) => !n.includes(".") && n.includes("-"); + function isRenderableType(g) { + if (typeof g === "function") return !isElementClass(g); + return typeof g === "object" && g !== null && typeof g.$$typeof === "symbol"; + } + function resolveDottedPath(root, name) { + let cur = root; + for (const seg of name.split(".")) { + if (cur == null) return void 0; + cur = cur[seg]; + } + return cur; + } + var GLOBAL_POLL_INTERVAL_MS = 50; + var GLOBAL_POLL_TIMEOUT_MS = 3e4; + function createExternalModules(onResolved) { + const cache = /* @__PURE__ */ new Map(); + let babelLoading = null; + const reportedMissing = /* @__PURE__ */ new Map(); + const polling = /* @__PURE__ */ new Set(); + function ensureBabel() { + if (window.Babel) return Promise.resolve(); + if (babelLoading) return babelLoading; + const babel = cdnScriptFor(BABEL_URL, BABEL_SRI); + babelLoading = new Promise((res, rej) => { + const s = document.createElement("script"); + s.src = babel.src; + if (babel.integrity) { + s.integrity = babel.integrity; + s.crossOrigin = "anonymous"; + } + s.onload = () => res(); + s.onerror = rej; + document.head.appendChild(s); + }); + return babelLoading; + } + const pending = /* @__PURE__ */ new Map(); + function load(kind, url, after) { + const existing = pending.get(url); + if (existing) return existing; + cache.set(url, null); + console.info("[dc-runtime] x-import: loading", url, "(" + kind + ")"); + const ready = Promise.all([ + kind === "jsx" ? ensureBabel() : Promise.resolve(), + after ?? Promise.resolve() + ]); + const p = ready.then(() => fetch(url)).then((r) => { + if (!r.ok) throw new Error("HTTP " + r.status); + return r.text(); + }).then((src) => { + const code = kind === "jsx" ? window.Babel.transform(src, { + filename: url, + presets: ["react", "typescript"] + }).code : src; + const module = { exports: {} }; + const before = new Set(Object.keys(window)); + //! nosemgrep: eval-and-function-constructor + new Function("React", "module", "exports", "require", code)( + getReact(), + module, + module.exports, + () => ({}) + ); + const globals = {}; + for (const k of Object.keys(window)) { + if (!before.has(k) && typeof window[k] === "function") { + globals[k] = window[k]; + } + } + cache.set(url, { mod: module.exports, globals }); + console.info( + "[dc-runtime] x-import: loaded", + url, + "\u2014 exports:", + Object.keys(module.exports), + "window globals:", + Object.keys(globals) + ); + onResolved(); + }).catch((e) => { + cache.set(url, { + mod: {}, + globals: {}, + error: "failed to load: " + (e instanceof Error && e.message ? e.message : String(e)) + }); + console.error( + "[dc-runtime] x-import: FAILED to load", + url, + "(" + kind + ")", + e + ); + onResolved(); + }); + pending.set(url, p); + return p; + } + function resolve2(url, name) { + const entry = cache.get(url); + if (!entry) return null; + const { mod, globals } = entry; + const C = mod && mod[name] || globals && globals[name] || typeof window !== "undefined" && window[name] || mod && mod.default; + if (typeof C === "function") return C; + const key = url + "\0" + name; + if (!reportedMissing.has(key)) { + reportedMissing.set( + key, + entry.error || 'no export named "' + name + '" (has: ' + Object.keys(mod).join(", ") + ")" + ); + console.error( + "[dc-runtime] x-import: module", + url, + "loaded but has no component named", + JSON.stringify(name), + "\u2014 available exports:", + Object.keys(mod), + "window globals:", + Object.keys(globals), + ". The module must `module.exports = {" + name + "}` or set `window." + name + "`." + ); + } + return null; + } + function waitForGlobal(name) { + if (polling.has(name)) return; + polling.add(name); + const started = Date.now(); + const isCE = isCustomElementName(name); + const tick = () => { + const found = isCE ? customElements.get(name) : isRenderableType(resolveDottedPath(window, name)); + if (found) { + polling.delete(name); + onResolved(); + return; + } + if (Date.now() - started >= GLOBAL_POLL_TIMEOUT_MS) { + console.warn( + "[dc-runtime] x-import: global", + JSON.stringify(name), + "never appeared on window after " + GLOBAL_POLL_TIMEOUT_MS + "ms" + ); + return; + } + setTimeout(tick, GLOBAL_POLL_INTERVAL_MS); + }; + setTimeout(tick, GLOBAL_POLL_INTERVAL_MS); + } + function resolveGlobal(url, name) { + const isCE = isCustomElementName(name); + if (!url) { + if (isCE) { + if (customElements.get(name)) return name; + waitForGlobal(name); + return null; + } + const g2 = resolveDottedPath(window, name); + if (isRenderableType(g2)) return g2; + waitForGlobal(name); + return null; + } + const entry = cache.get(url); + if (!entry) return null; + if (isCE && customElements.get(name)) return name; + const g = entry.globals[name] ?? resolveDottedPath(window, name); + if (isRenderableType(g)) return g; + if (name.includes(".")) return null; + const key = url + "\0global\0" + name; + if (!reportedMissing.has(key)) { + reportedMissing.set(key, null); + if (isCE && !customElements.get(name)) { + console.warn( + "[dc-runtime] x-import:", + url, + "loaded but no custom element", + JSON.stringify(name), + "is registered and window." + name + " is not a function \u2014 rendering <" + name + "> as an unknown element." + ); + } + } + return name; + } + function getError(url, name) { + const entry = cache.get(url); + if (entry?.error) return entry.error; + return reportedMissing.get(url + "\0" + name) || null; + } + return { load, resolve: resolve2, resolveGlobal, getError }; + } + function isElementClass(g) { + try { + return typeof g === "function" && typeof HTMLElement !== "undefined" && g.prototype instanceof HTMLElement; + } catch { + return false; + } + } + + // src/atomics.ts + var ATOMIC_CSS = ( + // layout + ".fx{display:flex}.col{display:flex;flex-direction:column}.grid{display:grid}.ac{align-items:center}.jc{justify-content:center}.jb{justify-content:space-between}.f1{flex:1}.noshrink{flex-shrink:0}.wrap{flex-wrap:wrap}.fw5{font-weight:500}.fw6{font-weight:600}.fw7{font-weight:700}.fw8{font-weight:800}.fs11{font-size:11px}.fs12{font-size:12px}.fs13{font-size:13px}.fs14{font-size:14px}.fs15{font-size:15px}.fs16{font-size:16px}.fs20{font-size:20px}.fs22{font-size:22px}.upper{text-transform:uppercase}.tc{text-align:center}.nowrap{white-space:nowrap}.gap8{gap:8px}.gap10{gap:10px}.gap12{gap:12px}.gap16{gap:16px}.gap24{gap:24px}.m0{margin:0}.mt8{margin-top:8px}.mt12{margin-top:12px}.mt16{margin-top:16px}.mb8{margin-bottom:8px}.mb12{margin-bottom:12px}.mb16{margin-bottom:16px}.posrel{position:relative}.posabs{position:absolute}.round{border-radius:50%}.ohide{overflow:hidden}.bbox{box-sizing:border-box}.pointer{cursor:pointer}.w100{width:100%}.b0{border:none}" + ); + + // src/helmet.ts + var DESIGN_DOC_MODE_RE = /]*\bname\s*=\s*["']design_doc_mode["'][^>]*\b(?:content|value)\s*=\s*["'](\w+)["']/i; + var CANVAS_BG_LIGHT = "#f0eee6"; + var CANVAS_BG_DARK = "#2e2c26"; + function createHelmetManager(doc, isStreaming) { + const mounted = /* @__PURE__ */ new Set(); + const live = /* @__PURE__ */ new Map(); + let designDocMode = null; + let canvasStyleEl = null; + let appTheme = "light"; + try { + const ds = doc.documentElement.dataset.theme; + appTheme = ds === "dark" || ds === "light" ? ds : new URLSearchParams(doc.defaultView?.location.search ?? "").get( + "theme" + ) === "dark" ? "dark" : "light"; + } catch { + } + function applyCanvasBg() { + if (!canvasStyleEl) return; + const bg = appTheme === "dark" ? CANVAS_BG_DARK : CANVAS_BG_LIGHT; + canvasStyleEl.textContent = `html,body{background:${bg}}#dc-root>.sc-host{position:relative}`; + } + function postDesignMode(mode) { + if (window.parent === window) return; + try { + window.parent.postMessage({ type: "__dc_design_mode", mode }, "*"); + } catch { + } + } + function setDesignDocMode(mode) { + if (mode === designDocMode) return; + designDocMode = mode; + postDesignMode(mode); + if (mode === "canvas") { + doc.documentElement.setAttribute("data-dc-canvas", ""); + canvasStyleEl = doc.createElement("style"); + canvasStyleEl.setAttribute("data-dc-canvas", ""); + applyCanvasBg(); + doc.head.appendChild(canvasStyleEl); + } else { + doc.documentElement.removeAttribute("data-dc-canvas"); + canvasStyleEl?.remove(); + canvasStyleEl = null; + } + } + window.addEventListener("message", (e) => { + const type = e.data && e.data.type; + if (type === "__dc_theme") { + const t = e.data.theme; + if (t === "light" || t === "dark") { + appTheme = t; + applyCanvasBg(); + } + return; + } + if (!designDocMode || type !== "__dc_probe") return; + postDesignMode(designDocMode); + }); + function compile(node) { + const raw = [...node.children]; + const helmetClosed = node.nextSibling != null || node.parentNode?.nextSibling != null; + if (node.hasAttribute("data-dc-atomics") && !mounted.has("__dc-atomics")) { + mounted.add("__dc-atomics"); + const el = doc.createElement("style"); + el.id = "__dc-atomics"; + el.textContent = ATOMIC_CSS; + doc.head.appendChild(el); + } + return (_vals, ctx) => { + const name = ctx && ctx.__name || ""; + const streaming = !!(name && isStreaming(name)); + for (let i = 0; i < raw.length; i++) { + const child = raw[i]; + const tag = child.tagName; + const mayBePartial = streaming && !helmetClosed && i === raw.length - 1; + if (tag === "SCRIPT") { + if (mayBePartial) continue; + const key = "SCRIPT|" + (child.getAttribute("src") || child.textContent || ""); + if (mounted.has(key)) continue; + mounted.add(key); + const el = doc.createElement("script"); + for (const { name: an, value } of [...child.attributes]) + el.setAttribute(an, value); + if (child.textContent) el.textContent = child.textContent; + doc.head.appendChild(el); + } else if (tag === "LINK" || tag === "META") { + if (mayBePartial) continue; + const key = tag + "|" + (child.getAttribute("href") || child.getAttribute("src") || child.outerHTML); + if (mounted.has(key)) continue; + mounted.add(key); + doc.head.appendChild(child.cloneNode(true)); + } else { + const key = name + "|" + i; + let el = live.get(key); + if (!el || el.tagName !== tag) { + if (el) el.remove(); + el = doc.createElement(tag.toLowerCase()); + live.set(key, el); + doc.head.appendChild(el); + } + for (const { name: an, value } of [...child.attributes]) { + if (el.getAttribute(an) !== value) el.setAttribute(an, value); + } + if (el.textContent !== child.textContent) + el.textContent = child.textContent; + } + } + return null; + }; + } + return { compile, setDesignDocMode }; + } + + // src/pseudo.ts + function createPseudoSheet(doc) { + let el = null; + const cache = /* @__PURE__ */ new Map(); + let n = 0; + return (pseudo, css) => { + const k = pseudo + "|" + css; + const hit = cache.get(k); + if (hit) return hit; + if (!el) { + el = doc.createElement("style"); + doc.head.appendChild(el); + } + const cls = "scp" + (n++).toString(36); + const sel = pseudo === "before" || pseudo === "after" ? "." + cls + "::" + pseudo : "." + cls + ":" + pseudo; + el.sheet.insertRule(sel + "{" + css + "}", el.sheet.cssRules.length); + cache.set(k, cls); + return cls; + }; + } + + // src/registry.ts + function createRegistry() { + const entries = /* @__PURE__ */ Object.create(null); + function get(name) { + return entries[name] || (entries[name] = { + html: "", + tpl: null, + Logic: null, + jsStreaming: false, + htmlStreaming: false, + ver: 0, + subs: /* @__PURE__ */ new Set(), + fetched: false + }); + } + function bump(name) { + const r = get(name); + r.ver++; + for (const fn of r.subs) fn(); + } + return { + entries, + get, + bump, + bumpAll() { + for (const n in entries) bump(n); + } + }; + } + + // src/runtime.ts + var COMPONENT_DIR = "."; + function createRuntime(doc = document) { + const registry = createRegistry(); + const pseudoClass = createPseudoSheet(doc); + const helmet = createHelmetManager( + doc, + (name) => registry.get(name).htmlStreaming + ); + const external = createExternalModules(() => registry.bumpAll()); + const factory = createComponentFactory(registry, ensureFetched); + const host = { + component: (name) => factory.getDC(name), + placeholder: (props) => h(Placeholder, props), + helmet: (node) => helmet.compile(node), + loadExternal: (kind, url, after) => external.load(kind, url, after), + resolveExternal: (url, name) => external.resolve(url, name), + resolveExternalGlobal: (url, name) => external.resolveGlobal(url, name), + resolveExternalError: (url, name) => external.getError(url, name), + pseudoClass + }; + function ensureFetched(name) { + const r = registry.get(name); + if (r.fetched) return; + r.fetched = true; + const url = COMPONENT_DIR + "/" + encodeURIComponent(name) + ".dc.html"; + fetch(url).then((res) => { + if (!res.ok) { + console.error( + '[dc-runtime] sibling fetch for "' + name + '" failed:', + url, + "returned", + res.status, + "\u2014 the reference renders as an empty placeholder." + ); + return ""; + } + return res.text(); + }).then((t) => { + if (!t) return; + const parsed = parseDcText(t); + if (!parsed) { + console.error( + '[dc-runtime] sibling fetch for "' + name + '":', + url, + "has no block \u2014 not a Design Component." + ); + return; + } + if (parsed.props) r.propsMeta = parsed.props; + if (parsed.preview) r.preview = parsed.preview; + if (parsed.template && !r.html) updateHtml(name, parsed.template); + if (parsed.js && !r.Logic) updateJs(name, parsed.js); + }).catch( + (e) => console.error( + '[dc-runtime] sibling fetch for "' + name + '" threw:', + url, + e + ) + ); + } + let rootName = null; + function updateHtml(name, html) { + const r = registry.get(name); + r.html = html; + if (name === rootName) { + const mode = DESIGN_DOC_MODE_RE.exec(html)?.[1] ?? null; + if (mode || !r.htmlStreaming) helmet.setDesignDocMode(mode); + } + try { + r.tpl = compileTemplate(html, host); + } catch (e) { + console.error("[dc-runtime] template compile FAILED for", name, e); + } + registry.bump(name); + } + function updateJs(name, src) { + const r = registry.get(name); + const seq = r.jsSeq = (r.jsSeq || 0) + 1; + try { + const Cls = evalDcLogic(src); + if (r.jsSeq !== seq) return; + if (typeof Cls !== "function") { + r.logicError = name + ".dc.html: + + diff --git a/docs/product/reference/project/veect-codebase/package.json b/docs/product/reference/project/veect-codebase/package.json new file mode 100644 index 0000000..92e5d90 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/package.json @@ -0,0 +1,47 @@ +{ + "name": "veect", + "private": true, + "version": "0.3.0", + "description": "Veect — design-system-native canvas. Your tokens and components in, production React out.", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc --noEmit && vite build", + "preview": "vite preview", + "lint": "eslint src --ext .ts,.tsx", + "format": "prettier --write \"src/**/*.{ts,tsx,css}\"", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dropdown-menu": "^2.1.1", + "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.2", + "@xyflow/react": "^12.3.2", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "lucide-react": "^0.428.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwind-merge": "^2.5.2", + "zustand": "^4.5.4" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.20", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.9", + "postcss": "^8.4.41", + "prettier": "^3.3.3", + "tailwindcss": "^3.4.10", + "typescript": "^5.5.4", + "vite": "^5.4.2" + } +} diff --git a/docs/product/reference/project/veect-codebase/postcss.config.js b/docs/product/reference/project/veect-codebase/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/docs/product/reference/project/veect-codebase/src/App.tsx b/docs/product/reference/project/veect-codebase/src/App.tsx new file mode 100644 index 0000000..2a872c3 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/App.tsx @@ -0,0 +1,48 @@ +import { useEffect } from 'react'; +import { EditorPage } from '@/pages/EditorPage'; +import { HomePage } from '@/pages/HomePage'; +import { OnboardingPage } from '@/pages/OnboardingPage'; +import { ProfilePage } from '@/pages/ProfilePage'; +import { SettingsPage } from '@/pages/SettingsPage'; +import { SignInPage } from '@/pages/SignInPage'; +import { SystemUpdatePage } from '@/pages/SystemUpdatePage'; +import { useVeect } from '@/store/veect'; +import type { Screen } from '@/types'; + +const PAGES: Record JSX.Element> = { + signin: SignInPage, + home: HomePage, + onboarding: OnboardingPage, + editor: EditorPage, + settings: SettingsPage, + profile: ProfilePage, + sysupdate: SystemUpdatePage, +}; + +export default function App() { + const screen = useVeect((s) => s.screen); + const undo = useVeect((s) => s.undo); + const redo = useVeect((s) => s.redo); + const select = useVeect((s) => s.select); + + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + const typing = ['INPUT', 'TEXTAREA'].includes((e.target as HTMLElement).tagName); + if (e.key === 'Escape') return select(null); + if (typing) return; + if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'z') { + e.preventDefault(); + (e.shiftKey ? redo : undo)(); + } + }; + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, [redo, select, undo]); + + const Page = PAGES[screen]; + return ( +
+ +
+ ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/atoms/Logo.tsx b/docs/product/reference/project/veect-codebase/src/components/atoms/Logo.tsx new file mode 100644 index 0000000..5835366 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/atoms/Logo.tsx @@ -0,0 +1,14 @@ +/** + * Atom — the Veect logo. Typographic: a tight semibold lowercase + * wordmark closed by a square ink terminal (the vector point). + * Inherits currentColor, so it holds in both themes and on the landing. + */ +export function Logo({ size = 14 }: { size?: number }) { + const sq = Math.max(3, Math.round(size * 0.28)); + return ( + + veect + + + ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/atoms/primitives.tsx b/docs/product/reference/project/veect-codebase/src/components/atoms/primitives.tsx new file mode 100644 index 0000000..fb9feab --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/atoms/primitives.tsx @@ -0,0 +1,28 @@ +import type { PropsWithChildren } from 'react'; +import { cn } from '@/lib/utils'; + +/** Atom — uppercase mono section label (the engraved-instrument voice). */ +export function MonoLabel({ children, className }: PropsWithChildren<{ className?: string }>) { + return ( + + {children} + + ); +} + +/** Atom — color swatch with an inset hairline so light colors hold shape. */ +export function Swatch({ hex, size = 16, radius = 2 }: { hex: string; size?: number; radius?: number }) { + return ( + + ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/molecules/ChatBubble.tsx b/docs/product/reference/project/veect-codebase/src/components/molecules/ChatBubble.tsx new file mode 100644 index 0000000..1a58a10 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/molecules/ChatBubble.tsx @@ -0,0 +1,36 @@ +import { Badge } from '@/components/ui/badge'; +import { cn } from '@/lib/utils'; +import type { ChatMessage } from '@/types'; + +/** Molecule — one chat turn. User turns sit right in accent-dim; assistant turns left in chip. */ +export function ChatBubble({ + message, + actions, +}: { + message: ChatMessage; + actions?: React.ReactNode; +}) { + const user = message.role === 'user'; + return ( +
+
+ {message.atts && message.atts.length > 0 && ( +
+ {message.atts.map((a) => ( + ▧ {a.name} + ))} +
+ )} + {message.text} + {actions} +
+
+ ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/molecules/ChatComposer.tsx b/docs/product/reference/project/veect-codebase/src/components/molecules/ChatComposer.tsx new file mode 100644 index 0000000..1cd2f62 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/molecules/ChatComposer.tsx @@ -0,0 +1,77 @@ +import { useState } from 'react'; +import { MonoLabel } from '@/components/atoms/primitives'; +import { Badge } from '@/components/ui/badge'; +import { Input } from '@/components/ui/input'; +import { Select } from '@/components/ui/select'; +import { truncate } from '@/lib/utils'; +import type { VeectNode } from '@/types'; + +/** + * Molecule — the chat composer: scope chip, input pill, send, model picker. + * Store-free: state in, events out. + */ +export function ChatComposer({ + targetNode, + onClearTarget, + onSubmit, + thinking, + model, + onModel, +}: { + targetNode: VeectNode | null; + onClearTarget: () => void; + onSubmit: (text: string) => void; + thinking: boolean; + model: string; + onModel: (m: string) => void; +}) { + const [draft, setDraft] = useState(''); + + const send = () => { + const text = draft.trim(); + if (!text || thinking) return; + setDraft(''); + onSubmit(text); + }; + + return ( +
+ {targetNode && ( +
+ + {targetNode.type} · {truncate(targetNode.name ?? targetNode.props.text ?? targetNode.type, 16)} + + +
+ )} +
+ + setDraft(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && send()} + placeholder={targetNode ? `refine further — scoped to this ${targetNode.type}…` : 'Describe a section — composed only from your system…'} + className="border-none bg-transparent px-0" + /> + +
+
+ + + constrained to your 10 components +
+
+ ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/molecules/CollapseButton.tsx b/docs/product/reference/project/veect-codebase/src/components/molecules/CollapseButton.tsx new file mode 100644 index 0000000..163d566 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/molecules/CollapseButton.tsx @@ -0,0 +1,30 @@ +import { cn } from '@/lib/utils'; + +/** + * Molecule — the collapse chevron shown inside an open panel. Panels + * reopen from the ActivityBar, so this is the only dismiss affordance + * a panel needs. + */ +export function CollapseButton({ + direction, + onCollapse, + className, +}: { + direction: 'left' | 'right'; + onCollapse: () => void; + className?: string; +}) { + return ( + + ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/molecules/LayerTree.tsx b/docs/product/reference/project/veect-codebase/src/components/molecules/LayerTree.tsx new file mode 100644 index 0000000..2bab4b9 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/molecules/LayerTree.tsx @@ -0,0 +1,121 @@ +import { useState } from 'react'; +import { NODE_GLYPHS } from '@/config/library'; +import { activeView, VIEW_ORDER, viewsOf, viewW, type ViewKind } from '@/engine/views'; +import { cn } from '@/lib/utils'; +import type { VeectNode } from '@/types'; + +const EyeIcon = ({ off }: { off?: boolean }) => ( + + + {off ? ( + + ) : ( + + )} + +); + +/** + * Molecule — collapsible layer tree. Under every frame row sits a + * clearly separated variant-chips row (desktop/tablet/mobile): the dot + * toggles a view's visibility, the label makes it the active view. + */ +export function LayerTree({ + frames, + selectedId, + onSelect, + onSetViews, +}: { + frames: VeectNode[]; + selectedId: string | null; + onSelect: (id: string) => void; + onSetViews: (frameId: string, views: ViewKind[], view: ViewKind) => void; +}) { + const [expanded, setExpanded] = useState>({}); + + const rows: Array<{ node: VeectNode; depth: number; branch: boolean }> = []; + const push = (node: VeectNode, depth: number) => { + const branch = node.children.length > 0; + rows.push({ node, depth, branch }); + if (expanded[node.id]) node.children.forEach((c) => push(c, depth + 1)); + }; + frames.forEach((f) => push(f, 0)); + + return ( +
+ {rows.map(({ node, depth, branch }) => ( +
+ + + {node.type === 'frame' && ( +
+ {VIEW_ORDER.map((k) => { + const views = viewsOf(node); + const av = activeView(node); + const on = views.includes(k); + const isAv = av === k && on; + return ( + + ); + })} +
+ )} +
+ ))} +
+ ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/molecules/LibraryList.tsx b/docs/product/reference/project/veect-codebase/src/components/molecules/LibraryList.tsx new file mode 100644 index 0000000..53914ae --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/molecules/LibraryList.tsx @@ -0,0 +1,73 @@ +import { LIBRARY, NODE_GLYPHS } from '@/config/library'; +import { CUSTOMER_LIB } from '@/config/demo'; +import { MonoLabel } from '@/components/atoms/primitives'; +import { Badge } from '@/components/ui/badge'; +import type { CustomComponent } from '@/types'; + +/** + * Molecule — the base kit + custom components list with mapping status, + * isolation triggers and the .tsx upload intake. Store-free. + */ +export function LibraryList({ + customComps, + onIsolate, + onUpload, +}: { + customComps: CustomComponent[]; + onIsolate: (type: string) => void; + onUpload: (file: File) => void; +}) { + return ( +
+ BASE KIT — MAPPED TO YOUR CODE + {LIBRARY.map((item) => ( +
+ + {NODE_GLYPHS[item.type]} + + {item.name} + + + {'primitive' in item && item.primitive ? 'html' : 'unmapped' in item && item.unmapped ? 'unmapped' : '@solstice/ui'} + +
+ ))} + + + + {customComps.length > 0 && CUSTOM — YOUR CODE} + {customComps.map((cc) => ( +
+ +
+ {cc.name} + {cc.file} +
+ +
+ ))} +
+ ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/molecules/PolishIssueCard.tsx b/docs/product/reference/project/veect-codebase/src/components/molecules/PolishIssueCard.tsx new file mode 100644 index 0000000..ca3d3e2 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/molecules/PolishIssueCard.tsx @@ -0,0 +1,36 @@ +import { Badge } from '@/components/ui/badge'; +import { cn } from '@/lib/utils'; +import type { PolishIssue } from '@/types'; + +const sevTone = { HIGH: 'danger', MED: 'warn', LOW: 'neutral' } as const; + +/** Molecule — one ranked craft issue with its why (teach) and one-click fix. */ +export function PolishIssueCard({ issue, onFix }: { issue: PolishIssue; onFix: () => void }) { + return ( +
+
+ {issue.sev} + + {issue.title} + +
+

{issue.why}

+ {issue.fixed ? ( + ✓ Fixed — in your tokens + ) : ( + + )} +
+ ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/molecules/SegmentedControl.tsx b/docs/product/reference/project/veect-codebase/src/components/molecules/SegmentedControl.tsx new file mode 100644 index 0000000..eb2502e --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/molecules/SegmentedControl.tsx @@ -0,0 +1,65 @@ +import { cn } from '@/lib/utils'; + +export interface SegmentedOption { + value: T; + label: string; +} + +/** Molecule — engraved segmented control (chip well + raised active seg). */ +export function SegmentedControl({ + options, + value, + onChange, + className, +}: { + options: SegmentedOption[]; + value: T; + onChange: (v: T) => void; + className?: string; +}) { + return ( +
+ {options.map((opt) => { + const active = opt.value === value; + return ( + + ); + })} +
+ ); +} + +/** Molecule — dial-like numeric stepper for token-bound values. */ +export function Stepper({ + value, + onStep, + format = (v) => `${v}px`, +}: { + value: number; + onStep: (delta: 1 | -1) => void; + format?: (v: number) => string; +}) { + const btn = + 'flex h-5 w-5 items-center justify-center rounded-[2px] border border-line-2 bg-raised text-[12px] text-ink-2 hover:text-ink'; + return ( +
+ + {format(value)} + +
+ ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/molecules/TokenEditor.tsx b/docs/product/reference/project/veect-codebase/src/components/molecules/TokenEditor.tsx new file mode 100644 index 0000000..315054a --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/molecules/TokenEditor.tsx @@ -0,0 +1,38 @@ +import { MonoLabel, Swatch } from '@/components/atoms/primitives'; +import type { DesignSystem } from '@/types'; + +const BRAND_OPTIONS = ['#E4572E', '#2A6FDB', '#1F8A5B', '#111827']; + +/** + * Molecule — the token editor: brand ramp + curated swatches + usage. + * Store-free; the parent owns the system and the ripple. + */ +export function TokenEditor({ + system, + usedCount, + onBrand, +}: { + system: DesignSystem; + usedCount: number; + onBrand: (hex: string) => void; +}) { + return ( +
+ COLOR — BRAND +
+ {([100, 300, 500, 700, 900] as const).map((k) => ( + + ))} +
+
+ {BRAND_OPTIONS.map((hex) => ( + + ))} +
+ brand-500 · used by {usedCount} components + RADIUS — {system.radius}px · TYPE — {system.font} +
+ ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/molecules/ZoomControls.tsx b/docs/product/reference/project/veect-codebase/src/components/molecules/ZoomControls.tsx new file mode 100644 index 0000000..95cf014 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/molecules/ZoomControls.tsx @@ -0,0 +1,40 @@ +import { useReactFlow, useViewport } from '@xyflow/react'; + +const btn = + 'px-2 py-1 font-mono text-[10.5px] text-ink-2 hover:bg-white/5 hover:text-ink'; + +/** + * Molecule — board zoom cluster, wired to the React Flow viewport. + * Must render inside to reach its context. + */ +export function ZoomControls() { + const { zoomIn, zoomOut, zoomTo, fitView } = useReactFlow(); + const { zoom } = useViewport(); + + return ( +
+
+ + + +
+ +
+ ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/organisms/ActivityBar.tsx b/docs/product/reference/project/veect-codebase/src/components/organisms/ActivityBar.tsx new file mode 100644 index 0000000..bb9de5a --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/organisms/ActivityBar.tsx @@ -0,0 +1,123 @@ +import type { ReactNode } from 'react'; +import { cn } from '@/lib/utils'; +import { useVeect } from '@/store/veect'; + +const Icon = ({ d, extra }: { d: string[]; extra?: ReactNode }) => ( + + {d.map((p, i) => ( + + ))} + {extra} + +); + +const ICONS: Record = { + chat: , + layers: , + library: ( + } + /> + ), + tokens: ( + + + + + } + /> + ), + inspect: ( + + + + + } + /> + ), + polish: , + code: , +}; + +interface Item { + key: string; + label: string; + icon: ReactNode; + active: boolean; + onClick: () => void; +} + +/** + * Organism — the leftmost activity bar: every panel is a toggleable + * icon, grouped by dividers (chat · board panels · detail panels · code). + */ +export function ActivityBar() { + const { chatOpen, boardRailCollapsed, inspectorCollapsed, codeOpen, setPanel } = useVeect(); + const boardTab = useVeect((s) => s.boardTab); + const setBoardTab = useVeect((s) => s.setBoardTab); + const inspTab = useVeect((s) => s.inspTab); + const setInspTab = useVeect((s) => s.setInspTab); + + const boardShown = !boardRailCollapsed; + const inspShown = !inspectorCollapsed; + + const openBoard = (tab: typeof boardTab) => () => { + if (boardShown && boardTab === tab) setPanel('board', false); + else { + setBoardTab(tab); + setPanel('board', true); + } + }; + const openInsp = (tab: typeof inspTab) => () => { + if (inspShown && inspTab === tab) setPanel('inspector', false); + else { + setInspTab(tab); + setPanel('inspector', true); + } + }; + + const groups: Item[][] = [ + [{ key: 'chat', label: 'Chat', icon: ICONS.chat, active: chatOpen, onClick: () => setPanel('chat', !chatOpen) }], + [ + { key: 'layers', label: 'Layers', icon: ICONS.layers, active: boardShown && boardTab === 'layers', onClick: openBoard('layers') }, + { key: 'library', label: 'Library', icon: ICONS.library, active: boardShown && boardTab === 'library', onClick: openBoard('library') }, + { key: 'tokens', label: 'Tokens', icon: ICONS.tokens, active: boardShown && boardTab === 'tokens', onClick: openBoard('tokens') }, + ], + [ + { key: 'inspect', label: 'Inspect', icon: ICONS.inspect, active: inspShown && inspTab === 'inspector', onClick: openInsp('inspector') }, + { key: 'polish', label: 'Polish', icon: ICONS.polish, active: inspShown && inspTab === 'polish', onClick: openInsp('polish') }, + ], + [{ key: 'code', label: 'Code', icon: ICONS.code, active: codeOpen, onClick: () => setPanel('code', !codeOpen) }], + ]; + + return ( + + ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/organisms/BoardCanvas.tsx b/docs/product/reference/project/veect-codebase/src/components/organisms/BoardCanvas.tsx new file mode 100644 index 0000000..66388a7 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/organisms/BoardCanvas.tsx @@ -0,0 +1,87 @@ +import { useCallback, useEffect, useState } from 'react'; +import { + applyNodeChanges, + Background, + BackgroundVariant, + ReactFlow, + type Node, + type NodeChange, +} from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import { FrameNode } from '@/components/organisms/FrameNode'; +import { ZoomControls } from '@/components/molecules/ZoomControls'; +import { findNode, frames } from '@/engine/tree'; +import { useVeect } from '@/store/veect'; +import type { VeectNode } from '@/types'; + +const nodeTypes = { frame: FrameNode }; + +const toFlowNodes = (board: VeectNode): Node[] => + frames(board).map((frame) => ({ + id: frame.id, + type: 'frame', + position: { x: frame.props.x ?? 0, y: frame.props.y ?? 0 }, + data: { frame }, + dragHandle: '.frame-drag-handle', + })); + +/** + * Organism — the infinite board on React Flow: scroll pans, ⌘/pinch + * zooms at the cursor, empty-pane drag pans, pane click deselects. + * The zustand tree stays the single source of truth — React Flow only + * carries frame positions and the viewport. + */ +export function BoardCanvas() { + const board = useVeect((s) => s.board); + const select = useVeect((s) => s.select); + const mutate = useVeect((s) => s.mutate); + const [nodes, setNodes] = useState(() => toFlowNodes(board)); + + // resync from the document store (add/delete/breakpoint/undo) + useEffect(() => setNodes(toFlowNodes(board)), [board]); + + const onNodesChange = useCallback( + (changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)), + [], + ); + + const onNodeDragStop = useCallback( + (_e: unknown, node: Node) => { + mutate((draft) => { + const frame = findNode(draft, node.id); + if (!frame) return; + frame.props.x = Math.round(node.position.x); + frame.props.y = Math.round(node.position.y); + }, 'Move frame'); + }, + [mutate], + ); + + return ( +
+ select(node.id)} + onPaneClick={() => select(null)} + fitView + fitViewOptions={{ padding: 0.18, maxZoom: 1 }} + minZoom={0.08} + maxZoom={2.5} + panOnScroll + zoomOnScroll={false} + zoomOnPinch + panOnDrag={[0, 1]} + nodesFocusable={false} + deleteKeyCode={null} + className="!bg-bg" + > + + + +
+ ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/organisms/BoardSidebar.tsx b/docs/product/reference/project/veect-codebase/src/components/organisms/BoardSidebar.tsx new file mode 100644 index 0000000..0f96460 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/organisms/BoardSidebar.tsx @@ -0,0 +1,83 @@ +import { LayerTree } from '@/components/molecules/LayerTree'; +import { LibraryList } from '@/components/molecules/LibraryList'; +import { TokenEditor } from '@/components/molecules/TokenEditor'; +import { CollapseButton } from '@/components/molecules/CollapseButton'; +import { MonoLabel } from '@/components/atoms/primitives'; +import { ramp } from '@/engine/color'; +import { parseComponentSource } from '@/engine/upload'; +import { frames, walk } from '@/engine/tree'; +import { withFrameViews } from '@/engine/views'; +import { useVeect } from '@/store/veect'; + +const TITLES = { layers: 'LAYERS', library: 'LIBRARY — YOUR SYSTEM', tokens: 'TOKENS' } as const; + +/** + * Organism — the board rail. The activity bar picks the tab; this is + * a thin shell around store-free molecules. + */ +export function BoardSidebar() { + const tab = useVeect((s) => s.boardTab); + const board = useVeect((s) => s.board); + const system = useVeect((s) => s.system); + const setSystem = useVeect((s) => s.setSystem); + const select = useVeect((s) => s.select); + const selectedId = useVeect((s) => s.selectedId); + const setPanel = useVeect((s) => s.setPanel); + const setIsolate = useVeect((s) => s.setIsolate); + const setInspTab = useVeect((s) => s.setInspTab); + const customComps = useVeect((s) => s.customComps); + const addCustomComp = useVeect((s) => s.addCustomComp); + const mutate = useVeect((s) => s.mutate); + + let brandUse = 0; + walk(board, (n) => { + if (['button', 'badge', 'avatar'].includes(n.type)) brandUse += 1; + }); + + return ( + + ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/organisms/ChatPanel.tsx b/docs/product/reference/project/veect-codebase/src/components/organisms/ChatPanel.tsx new file mode 100644 index 0000000..87074de --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/organisms/ChatPanel.tsx @@ -0,0 +1,66 @@ +import { MonoLabel } from '@/components/atoms/primitives'; +import { ChatBubble } from '@/components/molecules/ChatBubble'; +import { ChatComposer } from '@/components/molecules/ChatComposer'; +import { CollapseButton } from '@/components/molecules/CollapseButton'; +import { Button } from '@/components/ui/button'; +import { useAiCompose } from '@/hooks/useAiCompose'; +import { useVeect } from '@/store/veect'; + +/** + * Organism — the vertical chat column. Layout only: the constrained-AI + * orchestration lives in useAiCompose, the composer is a molecule. + */ +export function ChatPanel() { + const chat = useVeect((s) => s.chat); + const model = useVeect((s) => s.model); + const setModel = useVeect((s) => s.setModel); + const setPanel = useVeect((s) => s.setPanel); + const { submit, accept, discard, thinking, proposal, targetNode, clearTarget } = useAiCompose(); + + return ( + + ); +} diff --git a/docs/product/reference/project/veect-codebase/src/components/organisms/CodePanel.tsx b/docs/product/reference/project/veect-codebase/src/components/organisms/CodePanel.tsx new file mode 100644 index 0000000..740c8d0 --- /dev/null +++ b/docs/product/reference/project/veect-codebase/src/components/organisms/CodePanel.tsx @@ -0,0 +1,118 @@ +import { useMemo, useState } from 'react'; +import { CollapseButton } from '@/components/molecules/CollapseButton'; +import { buildCode } from '@/engine/codegen'; +import { parseGeneratedTsx } from '@/engine/parser'; +import { CUSTOMER_LIB } from '@/config/demo'; +import { useActiveFrame, useVeect, findNode } from '@/store/veect'; + +/** + * Organism — the live code companion. Deterministic tree → TSX with the + * customer's real imports; click a line to select its node. Edit mode + * round-trips: the same grammar parses back onto the canvas. + */ +export function CodePanel() { + const system = useVeect((s) => s.system); + const select = useVeect((s) => s.select); + const selectedId = useVeect((s) => s.selectedId); + const setPanel = useVeect((s) => s.setPanel); + const mutate = useVeect((s) => s.mutate); + const customComps = useVeect((s) => s.customComps); + const frame = useActiveFrame(); + + const [editing, setEditing] = useState(false); + const [draft, setDraft] = useState(''); + const [error, setError] = useState(null); + + const { lines } = useMemo( + () => (frame ? buildCode(frame, system, false, customComps) : { lines: [] }), + [frame, system, customComps], + ); + const text = useMemo(() => lines.map((l) => l.t).join('\n'), [lines]); + + const startEdit = () => { + setDraft(text); + setError(null); + setEditing(true); + }; + + const apply = () => { + if (!frame) return; + const result = parseGeneratedTsx(draft, customComps); + if ('error' in result) { + setError(`Line ${result.line}: couldn't read “${result.error}” — the editor speaks the generated grammar.`); + return; + } + mutate((board) => { + const f = findNode(board, frame.id); + if (!f) return; + f.children = result.children; + if (result.name) f.name = result.name; + }, 'Edited code by hand'); + setEditing(false); + setError(null); + }; + + return ( +