@iloveagents/foundry-web-ui 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/dist/components/ag-ui-runtime-provider.d.ts +64 -0
- package/dist/components/ag-ui-runtime-provider.js +71 -0
- package/dist/components/app-brand.d.ts +6 -0
- package/dist/components/app-brand.js +13 -0
- package/dist/components/assistant-chat.d.ts +26 -0
- package/dist/components/assistant-chat.js +93 -0
- package/dist/components/chat-attachments.d.ts +25 -0
- package/dist/components/chat-attachments.js +20 -0
- package/dist/components/chat-bubble.d.ts +1 -0
- package/dist/components/chat-bubble.js +103 -0
- package/dist/components/chat-header.d.ts +2 -0
- package/dist/components/chat-header.js +16 -0
- package/dist/components/client-tool-executor.d.ts +12 -0
- package/dist/components/client-tool-executor.js +108 -0
- package/dist/components/collection-empty-state.d.ts +9 -0
- package/dist/components/collection-empty-state.js +6 -0
- package/dist/components/collection-filter-bar.d.ts +19 -0
- package/dist/components/collection-filter-bar.js +28 -0
- package/dist/components/collection-search-input.d.ts +6 -0
- package/dist/components/collection-search-input.js +8 -0
- package/dist/components/collection-skeleton.d.ts +6 -0
- package/dist/components/collection-skeleton.js +10 -0
- package/dist/components/collection-sort-menu.d.ts +10 -0
- package/dist/components/collection-sort-menu.js +9 -0
- package/dist/components/collection-surface.d.ts +8 -0
- package/dist/components/collection-surface.js +5 -0
- package/dist/components/collection-toolbar.d.ts +9 -0
- package/dist/components/collection-toolbar.js +5 -0
- package/dist/components/collection-view-toggle.d.ts +7 -0
- package/dist/components/collection-view-toggle.js +17 -0
- package/dist/components/composer-submit-bridge.d.ts +1 -0
- package/dist/components/composer-submit-bridge.js +15 -0
- package/dist/components/confirm-dialog.d.ts +11 -0
- package/dist/components/confirm-dialog.js +6 -0
- package/dist/components/confirmation-card.d.ts +14 -0
- package/dist/components/confirmation-card.js +12 -0
- package/dist/components/context-badges.d.ts +16 -0
- package/dist/components/context-badges.js +56 -0
- package/dist/components/context-bar.d.ts +41 -0
- package/dist/components/context-bar.js +123 -0
- package/dist/components/form-dialog.d.ts +12 -0
- package/dist/components/form-dialog.js +13 -0
- package/dist/components/global-selection-popover.d.ts +7 -0
- package/dist/components/global-selection-popover.js +167 -0
- package/dist/components/infinite-scroll-sentinel.d.ts +6 -0
- package/dist/components/infinite-scroll-sentinel.js +25 -0
- package/dist/components/json-viewer.d.ts +10 -0
- package/dist/components/json-viewer.js +80 -0
- package/dist/components/loading-indicator.d.ts +4 -0
- package/dist/components/loading-indicator.js +22 -0
- package/dist/components/markdown-text.d.ts +13 -0
- package/dist/components/markdown-text.js +221 -0
- package/dist/components/name-dialog.d.ts +16 -0
- package/dist/components/name-dialog.js +23 -0
- package/dist/components/selection-popover.d.ts +12 -0
- package/dist/components/selection-popover.js +111 -0
- package/dist/components/show-document-tool-ui.d.ts +1 -0
- package/dist/components/show-document-tool-ui.js +53 -0
- package/dist/components/sidebar.d.ts +4 -0
- package/dist/components/sidebar.js +904 -0
- package/dist/components/surface-card.d.ts +9 -0
- package/dist/components/surface-card.js +33 -0
- package/dist/components/theme-runtime-provider.d.ts +15 -0
- package/dist/components/theme-runtime-provider.js +48 -0
- package/dist/components/theme-toggle.d.ts +2 -0
- package/dist/components/theme-toggle.js +19 -0
- package/dist/components/tool-call-card.d.ts +27 -0
- package/dist/components/tool-call-card.js +13 -0
- package/dist/components/tool-fallback.d.ts +26 -0
- package/dist/components/tool-fallback.js +129 -0
- package/dist/components/tool-panel-layout.d.ts +10 -0
- package/dist/components/tool-panel-layout.js +12 -0
- package/dist/components/tool-panel.d.ts +1 -0
- package/dist/components/tool-panel.js +89 -0
- package/dist/components/tooltip-icon-button.d.ts +7 -0
- package/dist/components/tooltip-icon-button.js +9 -0
- package/dist/components/user-menu.d.ts +2 -0
- package/dist/components/user-menu.js +14 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +83 -0
- package/dist/lib/ag-ui-adapter.d.ts +29 -0
- package/dist/lib/ag-ui-adapter.js +373 -0
- package/dist/lib/app-store.d.ts +115 -0
- package/dist/lib/app-store.js +199 -0
- package/dist/lib/attachment-adapter.d.ts +20 -0
- package/dist/lib/attachment-adapter.js +99 -0
- package/dist/lib/auth-provider.d.ts +16 -0
- package/dist/lib/auth-provider.js +88 -0
- package/dist/lib/chat-bubble-store.d.ts +30 -0
- package/dist/lib/chat-bubble-store.js +23 -0
- package/dist/lib/chat-lifecycle-store.d.ts +10 -0
- package/{src/lib/chat-lifecycle-store.ts → dist/lib/chat-lifecycle-store.js} +3 -13
- package/dist/lib/client-tools.d.ts +11 -0
- package/dist/lib/client-tools.js +274 -0
- package/dist/lib/composer-submit-store.d.ts +12 -0
- package/dist/lib/composer-submit-store.js +17 -0
- package/dist/lib/dev-store.d.ts +38 -0
- package/dist/lib/dev-store.js +32 -0
- package/dist/lib/nav-config.d.ts +159 -0
- package/dist/lib/nav-config.js +162 -0
- package/dist/lib/nav-dnd.d.ts +8 -0
- package/dist/lib/nav-dnd.js +9 -0
- package/dist/lib/nav-store.d.ts +45 -0
- package/dist/lib/nav-store.js +49 -0
- package/dist/lib/selection-context.d.ts +21 -0
- package/dist/lib/selection-context.js +34 -0
- package/dist/lib/sidebar-store.d.ts +23 -0
- package/dist/lib/sidebar-store.js +98 -0
- package/dist/lib/theme-runtime.d.ts +130 -0
- package/dist/lib/theme-runtime.js +479 -0
- package/dist/lib/theme-store.d.ts +15 -0
- package/dist/lib/theme-store.js +53 -0
- package/dist/lib/tool-panel-store.d.ts +48 -0
- package/dist/lib/tool-panel-store.js +69 -0
- package/dist/lib/use-new-conversation.d.ts +8 -0
- package/dist/lib/use-new-conversation.js +62 -0
- package/dist/lib/use-page-tools.d.ts +27 -0
- package/{src/lib/use-page-tools.ts → dist/lib/use-page-tools.js} +11 -13
- package/dist/styles.css +142 -0
- package/dist/ui/collapsible.d.ts +5 -0
- package/{src/ui/collapsible.tsx → dist/ui/collapsible.js} +0 -2
- package/dist/ui/dialog.d.ts +15 -0
- package/dist/ui/dialog.js +25 -0
- package/dist/ui/dropdown-menu.d.ts +7 -0
- package/dist/ui/dropdown-menu.js +19 -0
- package/dist/ui/popover.d.ts +7 -0
- package/dist/ui/popover.js +10 -0
- package/dist/ui/select.d.ts +4 -0
- package/dist/ui/select.js +9 -0
- package/dist/ui/tooltip.d.ts +7 -0
- package/dist/ui/tooltip.js +10 -0
- package/package.json +31 -12
- package/AGENTS.md +0 -59
- package/CHANGELOG.md +0 -203
- package/CLAUDE.md +0 -1
- package/src/__tests__/no-spaces-imports.test.ts +0 -59
- package/src/__tests__/open-core-guards.test.ts +0 -307
- package/src/__tests__/theme-runtime.test.ts +0 -81
- package/src/__tests__/tool-fallback.test.tsx +0 -109
- package/src/components/__tests__/context-bar.test.ts +0 -112
- package/src/components/ag-ui-runtime-provider.tsx +0 -170
- package/src/components/app-brand.tsx +0 -38
- package/src/components/assistant-chat.tsx +0 -461
- package/src/components/chat-attachments.tsx +0 -68
- package/src/components/chat-bubble.tsx +0 -373
- package/src/components/chat-header.tsx +0 -44
- package/src/components/client-tool-executor.tsx +0 -230
- package/src/components/collection-empty-state.tsx +0 -37
- package/src/components/collection-filter-bar.tsx +0 -168
- package/src/components/collection-search-input.tsx +0 -41
- package/src/components/collection-skeleton.tsx +0 -55
- package/src/components/collection-sort-menu.tsx +0 -62
- package/src/components/collection-surface.tsx +0 -46
- package/src/components/collection-toolbar.tsx +0 -33
- package/src/components/collection-view-toggle.tsx +0 -61
- package/src/components/composer-submit-bridge.tsx +0 -19
- package/src/components/confirm-dialog.tsx +0 -49
- package/src/components/confirmation-card.tsx +0 -32
- package/src/components/context-badges.tsx +0 -136
- package/src/components/context-bar.tsx +0 -238
- package/src/components/form-dialog.tsx +0 -56
- package/src/components/global-selection-popover.tsx +0 -266
- package/src/components/infinite-scroll-sentinel.tsx +0 -48
- package/src/components/json-viewer.tsx +0 -232
- package/src/components/loading-indicator.tsx +0 -40
- package/src/components/markdown-text-citations.test.tsx +0 -108
- package/src/components/markdown-text.tsx +0 -508
- package/src/components/name-dialog.tsx +0 -87
- package/src/components/selection-popover.tsx +0 -156
- package/src/components/show-document-tool-ui.tsx +0 -90
- package/src/components/sidebar.test.tsx +0 -611
- package/src/components/sidebar.tsx +0 -1689
- package/src/components/surface-card.tsx +0 -45
- package/src/components/theme-runtime-provider.tsx +0 -93
- package/src/components/theme-toggle.tsx +0 -27
- package/src/components/tool-call-card.tsx +0 -126
- package/src/components/tool-fallback.tsx +0 -169
- package/src/components/tool-panel-layout.tsx +0 -36
- package/src/components/tool-panel.tsx +0 -233
- package/src/components/tooltip-icon-button.tsx +0 -28
- package/src/components/user-menu.tsx +0 -60
- package/src/index.ts +0 -185
- package/src/lib/__tests__/ag-ui-adapter.test.ts +0 -273
- package/src/lib/__tests__/app-store.test.ts +0 -405
- package/src/lib/__tests__/attachment-adapter.test.ts +0 -48
- package/src/lib/__tests__/chat-bubble-store.test.ts +0 -67
- package/src/lib/__tests__/client-tools.test.ts +0 -124
- package/src/lib/__tests__/dev-store.test.ts +0 -78
- package/src/lib/__tests__/nav-config.test.ts +0 -135
- package/src/lib/__tests__/nav-dnd.test.ts +0 -24
- package/src/lib/__tests__/nav-store.test.ts +0 -59
- package/src/lib/__tests__/selection-context.test.ts +0 -114
- package/src/lib/__tests__/sidebar-store.test.ts +0 -144
- package/src/lib/__tests__/theme-store.test.ts +0 -83
- package/src/lib/__tests__/tool-panel-store.test.ts +0 -61
- package/src/lib/__tests__/use-page-context.test.ts +0 -58
- package/src/lib/ag-ui-adapter.ts +0 -412
- package/src/lib/app-store.ts +0 -361
- package/src/lib/attachment-adapter.ts +0 -92
- package/src/lib/auth-provider.tsx +0 -144
- package/src/lib/chat-bubble-store.ts +0 -46
- package/src/lib/client-tools.ts +0 -293
- package/src/lib/composer-submit-store.ts +0 -32
- package/src/lib/dev-store.ts +0 -69
- package/src/lib/nav-config.ts +0 -329
- package/src/lib/nav-dnd.ts +0 -14
- package/src/lib/nav-store.ts +0 -89
- package/src/lib/selection-context.ts +0 -65
- package/src/lib/sidebar-store.ts +0 -113
- package/src/lib/theme-runtime.ts +0 -660
- package/src/lib/theme-store.ts +0 -67
- package/src/lib/tool-panel-store.ts +0 -121
- package/src/lib/use-new-conversation.test.tsx +0 -168
- package/src/lib/use-new-conversation.ts +0 -64
- package/src/ui/dialog.tsx +0 -109
- package/src/ui/dropdown-menu.tsx +0 -75
- package/src/ui/popover.tsx +0 -34
- package/src/ui/select.tsx +0 -15
- package/src/ui/tooltip.tsx +0 -30
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -16
- package/vitest.config.ts +0 -8
package/CHANGELOG.md
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
# @iloveagents/foundry-web-ui
|
|
2
|
-
|
|
3
|
-
## 0.3.0
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- @iloveagents/foundry-agent@0.3.0
|
|
8
|
-
- @iloveagents/foundry-web-primitives@0.3.0
|
|
9
|
-
|
|
10
|
-
## 0.2.2
|
|
11
|
-
|
|
12
|
-
### Patch Changes
|
|
13
|
-
|
|
14
|
-
- 395f0cd: web-ui: popover New Thread didn't actually start a new thread
|
|
15
|
-
|
|
16
|
-
When the user clicks the compose / SquarePen "New Thread" button in
|
|
17
|
-
the floating chat popover (`ChatBubble` on workspace/entity pages with
|
|
18
|
-
`navigateToChat: false`), the runtime was supposed to discard the
|
|
19
|
-
current thread and start fresh. In practice the popover header
|
|
20
|
-
subtitle flipped to "New conversation" but the message list stayed
|
|
21
|
-
populated with the prior chat's messages — the button looked like a
|
|
22
|
-
no-op.
|
|
23
|
-
|
|
24
|
-
Root cause: `useNewConversation` bumped the AGUIRuntime inner key to
|
|
25
|
-
remount, but the parent `RuntimeBody`'s `effectiveThreadId`
|
|
26
|
-
(`urlMatch ?? sticky ?? freshIdRef.current`) stayed pinned to the
|
|
27
|
-
sticky active-chat id. The history adapter on the fresh inner mount
|
|
28
|
-
immediately loaded the same chat's messages from the server →
|
|
29
|
-
nothing visibly changed.
|
|
30
|
-
|
|
31
|
-
The sidebar "New Thread" button avoided this only by side-effect —
|
|
32
|
-
it navigates to `/`, which triggers `useTrackActiveChatFromUrl` to
|
|
33
|
-
clear sticky. The popover preserves the user's workspace context and
|
|
34
|
-
doesn't navigate, so sticky never cleared.
|
|
35
|
-
|
|
36
|
-
Fix: add a tiny DI store `useChatLifecycleStore` exposing an
|
|
37
|
-
`onNewConversation` callback. `useNewConversation` invokes it BEFORE
|
|
38
|
-
`resetThread` so the host (spaces-web-ui in lastspace) can clear its
|
|
39
|
-
sticky id; the next render's `effectiveThreadId` has already advanced
|
|
40
|
-
to a freshly-minted UUID, and the history adapter binds to that
|
|
41
|
-
instead of re-hydrating the prior chat.
|
|
42
|
-
|
|
43
|
-
Customers that don't track sticky ids leave `onNewConversation`
|
|
44
|
-
unset; the call site no-ops gracefully.
|
|
45
|
-
|
|
46
|
-
- 1ba01ef: web-ui(sidebar): align group-header labels (CHATS, WORKSPACES, ADMIN, …)
|
|
47
|
-
with the leftmost edge of their item labels
|
|
48
|
-
|
|
49
|
-
Previously the collapsible group header used `px-3` on the outer wrapper
|
|
50
|
-
and a `size-6` chevron, while item rows used `pl-5` (depth-0 indent) +
|
|
51
|
-
`size-3.5` icon + `gap-2`. Result: group labels landed at ~36px from the
|
|
52
|
-
sidebar edge while item labels landed at 42px — group labels drifted
|
|
53
|
-
right of the children they introduced and the visual rhythm broke.
|
|
54
|
-
|
|
55
|
-
Make the header's leading column a shared `SIDEBAR_ITEM_ICON_CLASS` +
|
|
56
|
-
`SIDEBAR_ITEM_GAP_CLASS` cell — the same constants item icons use — so
|
|
57
|
-
every group label (collapsible, link-style, or plain) lands at the same
|
|
58
|
-
x-coordinate as item labels. The chevron now sits in the icon column;
|
|
59
|
-
its hit-area is preserved at 28px via `-m-2 p-2` (negative-margin
|
|
60
|
-
absorbs the extra padding so the visual cell stays 14px and label
|
|
61
|
-
geometry doesn't shift).
|
|
62
|
-
|
|
63
|
-
Non-collapsible header branches get an explicit invisible spacer of the
|
|
64
|
-
same width so their labels align too.
|
|
65
|
-
|
|
66
|
-
- 8184a8c: shell: URL is authoritative for the runtime threadId — fixes
|
|
67
|
-
resume-creates-new-conversation race
|
|
68
|
-
|
|
69
|
-
`ChatConversationAwareRuntime` previously computed
|
|
70
|
-
`effectiveThreadId = sticky ?? urlMatch ?? freshIdRef.current`.
|
|
71
|
-
When the user navigated from one chat to another via a Recents
|
|
72
|
-
click, the synchronous render that followed the URL change had:
|
|
73
|
-
- `urlMatch = B` (read from the now-updated `useLocation`)
|
|
74
|
-
- `sticky = A` (the active-chat-store hadn't been updated by
|
|
75
|
-
`useTrackActiveChatFromUrl`'s `useEffect` yet — effects run
|
|
76
|
-
AFTER the commit phase)
|
|
77
|
-
|
|
78
|
-
`sticky ?? urlMatch` picked `A` (the previous chat). The AG-UI
|
|
79
|
-
adapter was constructed with `threadId = A`. The first message the
|
|
80
|
-
user sent went to `/api/agent` with `thread_id = A`, the middleware
|
|
81
|
-
created a new row keyed by `A`... wait actually no, here's what
|
|
82
|
-
happens — the runtime mints a fresh runner UUID when given a
|
|
83
|
-
mismatched threadId; that fresh UUID became a new conversation row,
|
|
84
|
-
appearing as "Untitled chat" at the top of the sidebar while the
|
|
85
|
-
URL still said `/chat/B`. Two active-looking dots: one for `B`
|
|
86
|
-
(NavLink URL match), one for the new row (statusDot from the
|
|
87
|
-
updated active-chat-store after the lazy ensure ran).
|
|
88
|
-
|
|
89
|
-
Swap the priority: `urlMatch ?? sticky ?? freshIdRef.current`. The
|
|
90
|
-
URL is authoritative whenever it's set (i.e. on `/chat/<id>`
|
|
91
|
-
routes), eliminating the stale-sticky race entirely. `sticky` is
|
|
92
|
-
still consulted as the second-priority fallback for non-chat
|
|
93
|
-
routes (`/spaces`, `/tasks`) so the popout chat stays "live" while
|
|
94
|
-
the user browses workspaces — that's the original purpose of
|
|
95
|
-
`useStickyConversationId` and it's preserved.
|
|
96
|
-
|
|
97
|
-
- Updated dependencies [8184a8c]
|
|
98
|
-
- @iloveagents/foundry-agent@0.2.2
|
|
99
|
-
- @iloveagents/foundry-web-primitives@0.2.2
|
|
100
|
-
|
|
101
|
-
## 0.2.1
|
|
102
|
-
|
|
103
|
-
### Patch Changes
|
|
104
|
-
|
|
105
|
-
- No source changes. Bumped in lock-step with the fixed group so the
|
|
106
|
-
whole group can publish at a version number that's free across all
|
|
107
|
-
four packages. (`ui@0.2.0` already published cleanly; this is a
|
|
108
|
-
follow-the-group patch bump.)
|
|
109
|
-
|
|
110
|
-
## 0.2.0
|
|
111
|
-
|
|
112
|
-
### Minor Changes
|
|
113
|
-
|
|
114
|
-
- UI: collapsible nav groups in `sidebar.tsx` with persisted state via
|
|
115
|
-
`useSyncExternalStore` + localStorage, plus stable group ordering via
|
|
116
|
-
new `priority` field in `nav-config.ts`.
|
|
117
|
-
|
|
118
|
-
`ag-ui-runtime-provider.tsx` accepts an external `threadId` and
|
|
119
|
-
`historyAdapterFactory` so the consuming app can wire its own
|
|
120
|
-
persistence (e.g. `ThreadHistoryAdapter` → REST `/api/conversations`).
|
|
121
|
-
|
|
122
|
-
### Patch Changes
|
|
123
|
-
|
|
124
|
-
- Move `@iloveagents/foundry-agent`, `-web-primitives` from
|
|
125
|
-
`peerDependencies` to `dependencies`. They always ship together as a
|
|
126
|
-
coordinated fixed group from this monorepo — they were never
|
|
127
|
-
independently-versioned peers. Declaring them as peerDeps caused
|
|
128
|
-
changesets' `shouldBumpMajor` cascade to promote the entire group to
|
|
129
|
-
a major version on every minor changeset. See the root `foundry-agent`
|
|
130
|
-
CHANGELOG entry for details.
|
|
131
|
-
|
|
132
|
-
- Updated dependencies
|
|
133
|
-
- @iloveagents/foundry-agent@0.2.0
|
|
134
|
-
- @iloveagents/foundry-web-primitives@0.2.0
|
|
135
|
-
|
|
136
|
-
### NOTE: 1.0.1 was accidental
|
|
137
|
-
|
|
138
|
-
Version 1.0.1 was published briefly on 2026-05-27 due to the peerDep
|
|
139
|
-
cascade bug described above (1.0.0 was reserved but not published —
|
|
140
|
-
1.0.1 was the retry). It has been unpublished. Both 1.0.0 and 1.0.1
|
|
141
|
-
are now permanently reserved on npm and CANNOT be re-published. Do
|
|
142
|
-
not depend on 1.0.x.
|
|
143
|
-
|
|
144
|
-
## 0.1.5
|
|
145
|
-
|
|
146
|
-
### Patch Changes
|
|
147
|
-
|
|
148
|
-
- f9144ca: Add `NavItem.statusIcon` slot for sidebar warning / status hints.
|
|
149
|
-
|
|
150
|
-
Renders a small tone-coloured icon next to the existing `statusDot`. Use for "this item needs attention" annotations (failed background jobs, permission drift, etc.) where a coloured dot isn't legible enough. Tones map to `warning` / `danger` / `info` / `neutral`. The `label` lands on both `aria-label` and the wrapping span's `title` attribute so screen-reader announcement + hover tooltip work without consumer wiring.
|
|
151
|
-
- @iloveagents/foundry-agent@0.1.5
|
|
152
|
-
- @iloveagents/foundry-web-primitives@0.1.5
|
|
153
|
-
|
|
154
|
-
## 0.1.4
|
|
155
|
-
|
|
156
|
-
### Patch Changes
|
|
157
|
-
|
|
158
|
-
- Updated dependencies [07ace7e]
|
|
159
|
-
- @iloveagents/foundry-agent@0.1.4
|
|
160
|
-
- @iloveagents/foundry-web-primitives@0.1.4
|
|
161
|
-
|
|
162
|
-
## 0.1.3
|
|
163
|
-
|
|
164
|
-
### Patch Changes
|
|
165
|
-
|
|
166
|
-
- Updated dependencies [d9b0460]
|
|
167
|
-
- @iloveagents/foundry-agent@0.1.3
|
|
168
|
-
- @iloveagents/foundry-web-primitives@0.1.3
|
|
169
|
-
|
|
170
|
-
## 0.1.2
|
|
171
|
-
|
|
172
|
-
### Patch Changes
|
|
173
|
-
|
|
174
|
-
- Updated dependencies [30a4346]
|
|
175
|
-
- @iloveagents/foundry-agent@0.1.2
|
|
176
|
-
- @iloveagents/foundry-web-primitives@0.1.2
|
|
177
|
-
|
|
178
|
-
## 0.1.1
|
|
179
|
-
|
|
180
|
-
### Patch Changes
|
|
181
|
-
|
|
182
|
-
- 689d3e9: feat(sidebar): distinct file-drop affordance for nav containers
|
|
183
|
-
|
|
184
|
-
`useNavItemDnd` now exposes `isFileDragOver` separately from `isDragOver`
|
|
185
|
-
so consuming nav items can render a prominent file-drop visual (dashed
|
|
186
|
-
primary outline + soft primary background + Upload icon) when the user
|
|
187
|
-
is dragging native files over the container. Previously file drags
|
|
188
|
-
shared the same subtle ring as entity-move drags, so users couldn't
|
|
189
|
-
tell that a folder accepted external files. Applies to all five nav-
|
|
190
|
-
item shapes (action-row leaf, button leaf, `NestedFolderItem`,
|
|
191
|
-
`CollapsibleNavItem` with children/actions, `NavLink` fallthrough) and
|
|
192
|
-
updates `ContainerDropZone` for visual parity.
|
|
193
|
-
|
|
194
|
-
Also fixes a related regression: the capture-phase
|
|
195
|
-
`onDragEnterCapture` / `onDragOverCapture` handlers were calling
|
|
196
|
-
`e.stopPropagation()`, which short-circuits React's synthetic dispatch
|
|
197
|
-
and prevented the bubble-phase `onDragEnter` (where state actually
|
|
198
|
-
mutates) from running. Removed — `preventDefault()` alone is enough to
|
|
199
|
-
mark the element as droppable.
|
|
200
|
-
|
|
201
|
-
- Updated dependencies [689d3e9]
|
|
202
|
-
- @iloveagents/foundry-agent@0.1.1
|
|
203
|
-
- @iloveagents/foundry-web-primitives@0.1.1
|
package/CLAUDE.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@AGENTS.md
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Guard test: @iloveagents/foundry-web-ui must never import from @lastspace/spaces-web-ui.
|
|
3
|
-
* This prevents SPACES code from leaking back into the React UI core package.
|
|
4
|
-
*/
|
|
5
|
-
import { describe, expect, it } from "vitest";
|
|
6
|
-
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
|
|
9
|
-
function findFiles(dir: string, ext: string[]): string[] {
|
|
10
|
-
const results: string[] = [];
|
|
11
|
-
for (const entry of readdirSync(dir)) {
|
|
12
|
-
const full = join(dir, entry);
|
|
13
|
-
if (entry === "node_modules" || entry === "__tests__") continue;
|
|
14
|
-
if (statSync(full).isDirectory()) {
|
|
15
|
-
results.push(...findFiles(full, ext));
|
|
16
|
-
} else if (ext.some((e) => full.endsWith(e))) {
|
|
17
|
-
results.push(full);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return results;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe("@iloveagents/foundry-web-ui import boundaries", () => {
|
|
24
|
-
const srcDir = join(import.meta.dirname!, "..");
|
|
25
|
-
const files = findFiles(srcDir, [".ts", ".tsx"]);
|
|
26
|
-
|
|
27
|
-
it("must not import from @lastspace/spaces-web-ui", () => {
|
|
28
|
-
const violations: string[] = [];
|
|
29
|
-
for (const file of files) {
|
|
30
|
-
const content = readFileSync(file, "utf-8");
|
|
31
|
-
if (content.includes("@lastspace/spaces-web-ui")) {
|
|
32
|
-
violations.push(file.replace(srcDir + "/", ""));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
expect(violations).toEqual([]);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("must not import from @content-editor/react (legacy name)", () => {
|
|
39
|
-
const violations: string[] = [];
|
|
40
|
-
for (const file of files) {
|
|
41
|
-
const content = readFileSync(file, "utf-8");
|
|
42
|
-
if (content.includes("@content-editor/react")) {
|
|
43
|
-
violations.push(file.replace(srcDir + "/", ""));
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
expect(violations).toEqual([]);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("must not use @/ path aliases (uses relative imports)", () => {
|
|
50
|
-
const violations: string[] = [];
|
|
51
|
-
for (const file of files) {
|
|
52
|
-
const content = readFileSync(file, "utf-8");
|
|
53
|
-
if (/from ["']@\//.test(content)) {
|
|
54
|
-
violations.push(file.replace(srcDir + "/", ""));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
expect(violations).toEqual([]);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Open-core packaging guards.
|
|
3
|
-
*
|
|
4
|
-
* Three data-driven guards (the customer-isolation + Python guards from the
|
|
5
|
-
* lastspace-origin version were dropped — foundry-ui is TS-only, no
|
|
6
|
-
* customer-specific code lives here):
|
|
7
|
-
*
|
|
8
|
-
* 1. Banned imports inside `packages/agent/src/` — no React, no DOM, no
|
|
9
|
-
* `@assistant-ui/*`, no bare `"zustand"`. The MSAL subpath is the
|
|
10
|
-
* single exception that may import `@azure/msal-browser`.
|
|
11
|
-
* 2. Open-core import-direction graph — each `@iloveagents/foundry-*`
|
|
12
|
-
* package may only import from a declared subset of siblings.
|
|
13
|
-
* 3. File-count ceiling on `packages/web-shell/src` — the shell stays
|
|
14
|
-
* thin (composition layer, not application surface).
|
|
15
|
-
* 4. Version-pin parity — shared deps in `pnpm-workspace.yaml` overrides
|
|
16
|
-
* stay in sync across workspace `package.json` files.
|
|
17
|
-
*/
|
|
18
|
-
import { describe, expect, it } from "vitest";
|
|
19
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
20
|
-
import { join, relative } from "node:path";
|
|
21
|
-
|
|
22
|
-
const ROOT = join(import.meta.dirname!, "../../../..");
|
|
23
|
-
|
|
24
|
-
// ----------------------------------------------------------------------------
|
|
25
|
-
// Configs
|
|
26
|
-
// ----------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Open-core package import graph. For each package, list which `@iloveagents/*`
|
|
30
|
-
* packages it MAY import. Empty array = none.
|
|
31
|
-
*
|
|
32
|
-
* Packages that do not yet exist on disk are skipped at runtime by
|
|
33
|
-
* `findPackageDir` so future packages can be wired in here ahead of their
|
|
34
|
-
* extraction.
|
|
35
|
-
*/
|
|
36
|
-
const OPEN_CORE_GRAPH: Record<string, string[]> = {
|
|
37
|
-
"@iloveagents/foundry-agent": [],
|
|
38
|
-
"@iloveagents/foundry-web-primitives": [],
|
|
39
|
-
"@iloveagents/foundry-web-ui": [
|
|
40
|
-
"@iloveagents/foundry-web-primitives",
|
|
41
|
-
"@iloveagents/foundry-agent",
|
|
42
|
-
],
|
|
43
|
-
"@iloveagents/foundry-web-shell": [
|
|
44
|
-
"@iloveagents/foundry-web-primitives",
|
|
45
|
-
"@iloveagents/foundry-web-ui",
|
|
46
|
-
"@iloveagents/foundry-agent",
|
|
47
|
-
],
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Banned bare-package imports inside `packages/agent/src/`.
|
|
52
|
-
*
|
|
53
|
-
* `@iloveagents/foundry-agent` is the cross-runtime transport — pure TS, zero React,
|
|
54
|
-
* zero `@assistant-ui/*`. The MSAL subpath (`packages/agent/src/msal/`) may
|
|
55
|
-
* pull in `@azure/msal-browser` (dynamic import keeps it optional). Bare
|
|
56
|
-
* `"zustand"` is banned everywhere — must use `"zustand/vanilla"`.
|
|
57
|
-
*/
|
|
58
|
-
const AGENT_BANNED_IMPORTS = [
|
|
59
|
-
{ specifier: "react", scope: "all" as const },
|
|
60
|
-
{ specifier: "react-dom", scope: "all" as const },
|
|
61
|
-
{ specifier: "react/jsx-runtime", scope: "all" as const },
|
|
62
|
-
{ specifierPrefix: "@assistant-ui/", scope: "all" as const },
|
|
63
|
-
{ specifier: "@azure/msal-react", scope: "all" as const },
|
|
64
|
-
// bare "zustand" is the React entry — agent must use "zustand/vanilla".
|
|
65
|
-
{ specifierExact: "zustand", scope: "all" as const },
|
|
66
|
-
// msal-browser is allowed only under src/msal/.
|
|
67
|
-
{ specifier: "@azure/msal-browser", scope: "non-msal" as const },
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
/** File-count ceiling: `packages/web-shell/src` is hard-capped at 15 — the
|
|
71
|
-
* shell is a thin composition layer (bootstrapShell + ChatModule wiring +
|
|
72
|
-
* auth provider seam), not application surface. Growth indicates leakage.
|
|
73
|
-
*/
|
|
74
|
-
const FILE_COUNT_CEILINGS = [
|
|
75
|
-
{ path: "packages/web-shell/src", ceiling: 15, hardFail: true },
|
|
76
|
-
];
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Shared deps where all consumers must agree on the major version.
|
|
80
|
-
* Rooted in `pnpm-workspace.yaml` overrides — once a customer's package.json
|
|
81
|
-
* declares an incompatible major, pnpm overrides hide the drift at install
|
|
82
|
-
* time, but the public package metadata is wrong.
|
|
83
|
-
*/
|
|
84
|
-
const SHARED_DEPS_REQUIRED_MAJOR = [
|
|
85
|
-
{ name: "react-router", expectedMajor: "7" },
|
|
86
|
-
{ name: "@assistant-ui/react", expectedMajor: "0" },
|
|
87
|
-
{ name: "@ag-ui/client", expectedMajor: "0" },
|
|
88
|
-
{ name: "@ag-ui/core", expectedMajor: "0" },
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
// ----------------------------------------------------------------------------
|
|
92
|
-
// Helpers
|
|
93
|
-
// ----------------------------------------------------------------------------
|
|
94
|
-
|
|
95
|
-
function findFiles(dir: string, exts: string[], skip: Set<string>): string[] {
|
|
96
|
-
if (!existsSync(dir)) return [];
|
|
97
|
-
const out: string[] = [];
|
|
98
|
-
for (const entry of readdirSync(dir)) {
|
|
99
|
-
if (skip.has(entry)) continue;
|
|
100
|
-
const full = join(dir, entry);
|
|
101
|
-
const stat = statSync(full);
|
|
102
|
-
if (stat.isDirectory()) out.push(...findFiles(full, exts, skip));
|
|
103
|
-
else if (exts.some((e) => full.endsWith(e))) out.push(full);
|
|
104
|
-
}
|
|
105
|
-
return out;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function findPackageDir(name: string): string | null {
|
|
109
|
-
const pkgDir = join(ROOT, "packages");
|
|
110
|
-
if (!existsSync(pkgDir)) return null;
|
|
111
|
-
for (const entry of readdirSync(pkgDir)) {
|
|
112
|
-
const candidate = join(pkgDir, entry);
|
|
113
|
-
const pkgJsonPath = join(candidate, "package.json");
|
|
114
|
-
if (existsSync(pkgJsonPath)) {
|
|
115
|
-
try {
|
|
116
|
-
const json = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
117
|
-
if (json.name === name) return candidate;
|
|
118
|
-
} catch {
|
|
119
|
-
/* ignore malformed */
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function importsOf(content: string): string[] {
|
|
127
|
-
const out: string[] = [];
|
|
128
|
-
// ES imports + dynamic imports + re-exports
|
|
129
|
-
const re = /(?:import|export|require)\s*[^"';]*?["']([^"']+)["']/g;
|
|
130
|
-
let m: RegExpExecArray | null;
|
|
131
|
-
while ((m = re.exec(content)) !== null) out.push(m[1]);
|
|
132
|
-
return out;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ----------------------------------------------------------------------------
|
|
136
|
-
// Tests
|
|
137
|
-
// ----------------------------------------------------------------------------
|
|
138
|
-
|
|
139
|
-
describe("open-core guards", () => {
|
|
140
|
-
// 1. Agent package: no React / no assistant-ui / no bare zustand
|
|
141
|
-
describe("@iloveagents/foundry-agent banned-import guard", () => {
|
|
142
|
-
const agentRoot = findPackageDir("@iloveagents/foundry-agent");
|
|
143
|
-
|
|
144
|
-
if (!agentRoot) {
|
|
145
|
-
it.skip("agent package not yet extracted", () => undefined);
|
|
146
|
-
} else {
|
|
147
|
-
const files = findFiles(
|
|
148
|
-
join(agentRoot, "src"),
|
|
149
|
-
[".ts", ".tsx"],
|
|
150
|
-
new Set(["node_modules", "dist", "__tests__"]),
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
function isUnderMsal(filePath: string): boolean {
|
|
154
|
-
const rel = relative(agentRoot!, filePath);
|
|
155
|
-
return rel.startsWith(`src/msal/`) || rel.startsWith(`src${"/"}msal${"/"}`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
for (const rule of AGENT_BANNED_IMPORTS) {
|
|
159
|
-
const label =
|
|
160
|
-
(rule as { specifier?: string }).specifier ??
|
|
161
|
-
(rule as { specifierPrefix?: string }).specifierPrefix ??
|
|
162
|
-
(rule as { specifierExact?: string }).specifierExact!;
|
|
163
|
-
|
|
164
|
-
it(`no source file imports "${label}" (scope: ${rule.scope})`, () => {
|
|
165
|
-
const violations: string[] = [];
|
|
166
|
-
for (const file of files) {
|
|
167
|
-
if (rule.scope === "non-msal" && isUnderMsal(file)) continue;
|
|
168
|
-
const content = readFileSync(file, "utf-8");
|
|
169
|
-
for (const imp of importsOf(content)) {
|
|
170
|
-
const matches =
|
|
171
|
-
("specifierExact" in rule && imp === rule.specifierExact) ||
|
|
172
|
-
("specifier" in rule && imp === rule.specifier) ||
|
|
173
|
-
("specifierPrefix" in rule &&
|
|
174
|
-
rule.specifierPrefix !== undefined &&
|
|
175
|
-
imp.startsWith(rule.specifierPrefix));
|
|
176
|
-
if (matches) violations.push(`${relative(ROOT, file)} -> ${imp}`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
expect(
|
|
180
|
-
violations,
|
|
181
|
-
`${label} import leaked into packages/agent/src/${
|
|
182
|
-
rule.scope === "non-msal" ? " (outside msal/)" : ""
|
|
183
|
-
}`,
|
|
184
|
-
).toEqual([]);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// 2. Open-core import graph
|
|
191
|
-
describe("open-core package import graph", () => {
|
|
192
|
-
for (const [pkg, allowed] of Object.entries(OPEN_CORE_GRAPH)) {
|
|
193
|
-
it(`${pkg} only imports from [${allowed.join(", ") || "none"}]`, () => {
|
|
194
|
-
const dir = findPackageDir(pkg);
|
|
195
|
-
if (!dir) return; // package not yet extracted — skip
|
|
196
|
-
const files = findFiles(
|
|
197
|
-
join(dir, "src"),
|
|
198
|
-
[".ts", ".tsx"],
|
|
199
|
-
new Set(["node_modules", "dist", "__tests__"]),
|
|
200
|
-
);
|
|
201
|
-
const violations: string[] = [];
|
|
202
|
-
for (const file of files) {
|
|
203
|
-
const content = readFileSync(file, "utf-8");
|
|
204
|
-
for (const imp of importsOf(content)) {
|
|
205
|
-
// Foundry-ui packages must never import from @lastspace/* (commercial
|
|
206
|
-
// downstream); allow-listing is enforced for sibling @iloveagents/* deps.
|
|
207
|
-
if (imp.startsWith("@lastspace/")) {
|
|
208
|
-
violations.push(`${relative(ROOT, file)} -> ${imp} (lastspace import forbidden)`);
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
if (!imp.startsWith("@iloveagents/")) continue;
|
|
212
|
-
// Self-imports allowed (subpath exports etc).
|
|
213
|
-
if (imp === pkg || imp.startsWith(`${pkg}/`)) continue;
|
|
214
|
-
// Strip subpath off the imported package.
|
|
215
|
-
const importedPkg = imp.split("/").slice(0, 2).join("/");
|
|
216
|
-
if (!allowed.includes(importedPkg)) {
|
|
217
|
-
violations.push(`${relative(ROOT, file)} -> ${imp}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
expect(violations, `${pkg} imported a forbidden package`).toEqual([]);
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// 3. File-count ceiling (warning + optional hard-fail)
|
|
227
|
-
describe("file-count ceilings on host directories", () => {
|
|
228
|
-
for (const { path, ceiling, hardFail } of FILE_COUNT_CEILINGS) {
|
|
229
|
-
it(`${path} ${hardFail ? "must" : "should"} stay ≤ ${ceiling} files`, () => {
|
|
230
|
-
const dir = join(ROOT, path);
|
|
231
|
-
if (!existsSync(dir)) return;
|
|
232
|
-
const files = findFiles(
|
|
233
|
-
dir,
|
|
234
|
-
[".ts", ".tsx", ".py"],
|
|
235
|
-
new Set(["node_modules", ".venv", "__pycache__", "dist", "__tests__"]),
|
|
236
|
-
);
|
|
237
|
-
if (hardFail) {
|
|
238
|
-
expect(
|
|
239
|
-
files.length,
|
|
240
|
-
`${path} has ${files.length} files; ceiling is ${ceiling}. Move features into packages.`,
|
|
241
|
-
).toBeLessThanOrEqual(ceiling);
|
|
242
|
-
} else {
|
|
243
|
-
// Warning-mode: log if over ceiling but don't fail. Flip to hardFail
|
|
244
|
-
// once host-shell extraction (#98) lands.
|
|
245
|
-
if (files.length > ceiling) {
|
|
246
|
-
// eslint-disable-next-line no-console
|
|
247
|
-
console.warn(
|
|
248
|
-
`[file-count guard] ${path} has ${files.length} files (ceiling ${ceiling}). Hard-fail enabled once #98 lands.`,
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
expect(files.length).toBeGreaterThanOrEqual(0); // always passes; just records
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// 4. Version-pin parity
|
|
258
|
-
describe("shared deps stay in sync across workspace package.jsons", () => {
|
|
259
|
-
function workspacePackageJsons(): { dir: string; json: Record<string, unknown> }[] {
|
|
260
|
-
const out: { dir: string; json: Record<string, unknown> }[] = [];
|
|
261
|
-
for (const base of ["packages"]) {
|
|
262
|
-
const baseDir = join(ROOT, base);
|
|
263
|
-
if (!existsSync(baseDir)) continue;
|
|
264
|
-
for (const entry of readdirSync(baseDir)) {
|
|
265
|
-
const dir = join(baseDir, entry);
|
|
266
|
-
const pkgJson = join(dir, "package.json");
|
|
267
|
-
if (existsSync(pkgJson)) {
|
|
268
|
-
try {
|
|
269
|
-
out.push({ dir, json: JSON.parse(readFileSync(pkgJson, "utf-8")) });
|
|
270
|
-
} catch {
|
|
271
|
-
/* ignore */
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return out;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function depMajor(spec: string): string | null {
|
|
280
|
-
// Strip leading ^, ~, >=, =, etc.
|
|
281
|
-
const m = spec.match(/(\d+)/);
|
|
282
|
-
return m ? m[1] : null;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
for (const { name, expectedMajor } of SHARED_DEPS_REQUIRED_MAJOR) {
|
|
286
|
-
it(`${name} declared at major ${expectedMajor} in every package.json that declares it`, () => {
|
|
287
|
-
const violations: string[] = [];
|
|
288
|
-
for (const { dir, json } of workspacePackageJsons()) {
|
|
289
|
-
const buckets = ["dependencies", "peerDependencies", "devDependencies"] as const;
|
|
290
|
-
for (const bucket of buckets) {
|
|
291
|
-
const deps = (json[bucket] ?? {}) as Record<string, string>;
|
|
292
|
-
const spec = deps[name];
|
|
293
|
-
if (!spec) continue;
|
|
294
|
-
const major = depMajor(spec);
|
|
295
|
-
if (major !== expectedMajor) {
|
|
296
|
-
violations.push(`${relative(ROOT, dir)} ${bucket}.${name}=${spec}`);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
expect(
|
|
301
|
-
violations,
|
|
302
|
-
`${name} version drift detected; pnpm-workspace.yaml override hides it at install time but published metadata is wrong`,
|
|
303
|
-
).toEqual([]);
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
});
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
getThemePreset,
|
|
4
|
-
mergeThemeDefinitions,
|
|
5
|
-
resolveThemeRuntime,
|
|
6
|
-
} from "../lib/theme-runtime.ts";
|
|
7
|
-
|
|
8
|
-
describe("theme runtime", () => {
|
|
9
|
-
it("merges sparse definitions in precedence order", () => {
|
|
10
|
-
const merged = mergeThemeDefinitions(
|
|
11
|
-
{
|
|
12
|
-
light: {
|
|
13
|
-
primary: "oklch(0.4 0.2 280)",
|
|
14
|
-
foreground: "oklch(0.2 0.01 280)",
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
light: {
|
|
19
|
-
primary: "oklch(0.5 0.19 260)",
|
|
20
|
-
},
|
|
21
|
-
status: {
|
|
22
|
-
light: {
|
|
23
|
-
success: "oklch(0.6 0.15 150)",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
expect(merged.light?.primary).toBe("oklch(0.5 0.19 260)");
|
|
30
|
-
expect(merged.light?.foreground).toBe("oklch(0.2 0.01 280)");
|
|
31
|
-
expect(merged.status?.light?.success).toBe("oklch(0.6 0.15 150)");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("resolves scoped layers on top of the preset", () => {
|
|
35
|
-
const runtime = resolveThemeRuntime(
|
|
36
|
-
[
|
|
37
|
-
{
|
|
38
|
-
id: "global-theme",
|
|
39
|
-
basePreset: "foundry-violet",
|
|
40
|
-
definition: {
|
|
41
|
-
light: {
|
|
42
|
-
primary: "oklch(0.45 0.18 300)",
|
|
43
|
-
},
|
|
44
|
-
sidebar: {
|
|
45
|
-
light: {
|
|
46
|
-
sidebar: "oklch(0.98 0.01 300)",
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
id: "workspace-theme",
|
|
53
|
-
definition: {
|
|
54
|
-
light: {
|
|
55
|
-
primary: "oklch(0.52 0.16 240)",
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
"light",
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
expect(runtime.mode).toBe("light");
|
|
64
|
-
expect(runtime.sources.map((layer) => layer.id)).toEqual([
|
|
65
|
-
"global-theme",
|
|
66
|
-
"workspace-theme",
|
|
67
|
-
]);
|
|
68
|
-
expect(runtime.definition.light?.primary).toBe("oklch(0.52 0.16 240)");
|
|
69
|
-
expect(runtime.variables["--primary" as keyof typeof runtime.variables]).toBe(
|
|
70
|
-
"oklch(0.52 0.16 240)",
|
|
71
|
-
);
|
|
72
|
-
expect(runtime.variables["--sidebar" as keyof typeof runtime.variables]).toBe(
|
|
73
|
-
"oklch(0.98 0.01 300)",
|
|
74
|
-
);
|
|
75
|
-
expect(runtime.variables["--success" as keyof typeof runtime.variables]).toBeTruthy();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("falls back to the default preset for unknown names", () => {
|
|
79
|
-
expect(getThemePreset("unknown-preset")).toEqual(getThemePreset("foundry-violet"));
|
|
80
|
-
});
|
|
81
|
-
});
|