@salsita-ai/sdk 0.50.0-develop.abef6d86a1 → 0.50.0-develop.ae52227c7e
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 +2 -2
- package/dist/application-spec/errors/diagnostic-codes.d.ts +1 -0
- package/dist/application-spec/errors/diagnostic-codes.js +1 -1
- package/dist/application-spec/errors/diagnostic-messages.d.ts +1 -0
- package/dist/application-spec/errors/diagnostic-messages.js +1 -1
- package/dist/application-spec/generate-application/code/project-scaffolding.d.ts +7 -0
- package/dist/application-spec/generate-application/code/project-scaffolding.js +1 -1
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-domain-block.js +25 -23
- package/dist/application-spec/generate-application/code/templates/domain.js +3 -3
- package/dist/application-spec/generate-application/code/templates/package-json.js +1 -1
- package/dist/application-spec/generate-application/code/templates/skills/sdk-content.d.ts +1 -0
- package/dist/application-spec/generate-application/code/templates/skills/sdk-content.js +1 -1
- package/dist/application-spec/parse-application-files/spec/model/defaults.d.ts +5 -0
- package/dist/application-spec/parse-application-files/spec/model/defaults.js +1 -1
- package/dist/configurator/pages/DefaultConfiguratorLayout.d.ts +6 -0
- package/dist/configurator/pages/DefaultConfiguratorLayout.js +1 -0
- package/dist/configurator/pages/LoadedConfiguratorDetailPage.js +1 -1
- package/dist/configurator/ui/configurator-layout/universal/DesktopLayout.js +4 -4
- package/dist/configurator/ui/configurator-layout/universal/DesktopPreview.js +1 -1
- package/dist/configurator/ui/configurator-layout/universal/MobileLayout.js +4 -4
- package/dist/configurator/ui/configurator-layout/universal/MobilePreview.js +1 -1
- package/dist/configurator/ui/configurator-layout/universal/use-layout-scene-safe-area.d.ts +47 -0
- package/dist/configurator/ui/configurator-layout/universal/use-layout-scene-safe-area.js +1 -0
- package/dist/configurator/ui/configurator-layout/universal/use-scene-safe-area.d.ts +11 -26
- package/dist/configurator/ui/configurator-layout/universal/use-scene-safe-area.js +1 -1
- package/dist/docs/complex-use-cases/filtered-controls-with-fix-dialog.mdx +16 -19
- package/dist/docs/complex-use-cases/sketching.mdx +0 -38
- package/dist/docs/controls/select-attribute/cardselect.mdx +9 -13
- package/dist/docs/index.json +1 -1
- package/dist/docs/integrations/woocommerce/local-development.mdx +1 -1
- package/dist/docs/other-features/side-effects.mdx +8 -11
- package/dist/docs/other-features/violation-messages.mdx +52 -56
- package/dist/docs/phoenix/configurator-interface.mdx +100 -65
- package/dist/docs/phoenix/phoenix-cli.mdx +19 -26
- package/dist/docs/phoenix/project-structure.mdx +8 -6
- package/dist/docs/phoenix/quickstart.mdx +2 -2
- package/dist/docs/phoenix/sdk-builder-workflow.mdx +2 -2
- package/dist/docs/phoenix/webhooks.mdx +1 -1
- package/dist/domain/core/component/domain-component-optic.d.ts +10 -17
- package/dist/domain/core/component/domain-component-optic.js +2 -2
- package/dist/domain/core/component/legacy-filter.d.ts +2 -17
- package/dist/domain/core/component/system-component-filter.d.ts +21 -16
- package/dist/domain/core/invariant/custom-invariants.d.ts +6 -14
- package/dist/domain/core/invariant/invariants.d.ts +1 -4
- package/dist/domain/core/state/migrations.d.ts +14 -45
- package/dist/domain/core/state/migrations.js +1 -1
- package/dist/domain/domain-builder.js +1 -1
- package/dist/domain/domain-runtime/spec/create-violation-expressions.d.ts +24 -0
- package/dist/domain/domain-runtime/spec/create-violation-expressions.js +1 -0
- package/dist/domain/domain-runtime/spec/index.d.ts +1 -1
- package/dist/domain/domain-runtime/spec/index.js +1 -1
- package/dist/domain/domain-runtime/spec/module-checks/index.d.ts +1 -1
- package/dist/domain/domain-runtime/spec/module-checks/resolve-invariant-modules.d.ts +2 -2
- package/dist/legacy/domain-cli/gen/invariants.js +3 -4
- package/dist/legacy/domain-cli/utils/cache.d.ts +1 -1
- package/dist/legacy/domain-cli/utils/cache.js +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/claude-md.d.ts +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/cursor-rules.d.ts +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/shared-sdk-content.d.ts +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/shared-sdk-content.js +1 -0
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/domain-index.js +17 -15
- package/dist/phoenix/application-spec/application-spec-builder.js +1 -1
- package/dist/phoenix/commands/init.d.ts +4 -4
- package/dist/phoenix/commands/init.js +1 -1
- package/dist/phoenix/commands/prettier.js +1 -1
- package/dist/phoenix/utils/git/index.d.ts +1 -0
- package/dist/phoenix/utils/git/index.js +1 -0
- package/dist/phoenix/utils/git/is-inside-git-repository.d.ts +6 -0
- package/dist/phoenix/utils/git/is-inside-git-repository.js +1 -0
- package/dist/phoenix/utils/index.d.ts +1 -0
- package/dist/phoenix/utils/index.js +1 -1
- package/package.json +3 -3
- package/dist/domain/domain-runtime/spec/invariant-builder.d.ts +0 -26
- package/dist/domain/domain-runtime/spec/invariant-builder.js +0 -1
|
@@ -25,8 +25,9 @@ The configurator interface is generated in `.phoenix/configurator-interface.ts`:
|
|
|
25
25
|
|
|
26
26
|
```typescript
|
|
27
27
|
// .phoenix/configurator-interface.ts (AUTO-GENERATED - DO NOT EDIT)
|
|
28
|
-
import {
|
|
29
|
-
|
|
28
|
+
import { createConfiguratorInterface } from '@salsita-ai/sdk/configurator'
|
|
29
|
+
|
|
30
|
+
import { configuratorBuilder } from './domain.js'
|
|
30
31
|
|
|
31
32
|
export const configuratorInterface = createConfiguratorInterface(configuratorBuilder)
|
|
32
33
|
|
|
@@ -64,9 +65,9 @@ type CustomMaterial = Phx['AdminModel']['CustomMaterial']
|
|
|
64
65
|
Access the configurator interface via `phx.frontend`:
|
|
65
66
|
|
|
66
67
|
```typescript
|
|
67
|
-
export const CustomComponent = () => {
|
|
68
|
+
export const CustomComponent = () => {
|
|
68
69
|
// Access domain systems
|
|
69
|
-
const {
|
|
70
|
+
const { navigationSystem, storeSystem } = phx.frontend.useDomain()
|
|
70
71
|
|
|
71
72
|
// Entity state hooks
|
|
72
73
|
const rootEntity = phx.frontend.useRootEntityState()
|
|
@@ -76,11 +77,11 @@ export const CustomComponent = () => {'{'}
|
|
|
76
77
|
const selectEntity = phx.frontend.useSelectEntity()
|
|
77
78
|
|
|
78
79
|
// Type-safe entity access
|
|
79
|
-
if (selectedEntity && Cabinet.is(selectedEntity)) {
|
|
80
|
+
if (selectedEntity && Cabinet.is(selectedEntity)) {
|
|
80
81
|
// selectedEntity is now typed as $Cabinet
|
|
81
82
|
console.log(selectedEntity.width, selectedEntity.$area)
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
}
|
|
84
|
+
}
|
|
84
85
|
```
|
|
85
86
|
|
|
86
87
|
## Core Hooks
|
|
@@ -90,16 +91,16 @@ The configurator interface provides 100+ hooks organized by category. Use TypeSc
|
|
|
90
91
|
### Domain & Application Context
|
|
91
92
|
|
|
92
93
|
```typescript
|
|
93
|
-
const ExampleComponent = () => {
|
|
94
|
+
const ExampleComponent = () => {
|
|
94
95
|
// Core domain systems
|
|
95
|
-
const {
|
|
96
|
+
const { navigationSystem, storeSystem, entitySystem } = phx.frontend.useDomain()
|
|
96
97
|
|
|
97
98
|
// Domain application context
|
|
98
|
-
const {
|
|
99
|
+
const { configurationPersistSystem } = phx.frontend.useDomainApplication()
|
|
99
100
|
|
|
100
101
|
// Full configurator context
|
|
101
|
-
const {
|
|
102
|
-
|
|
102
|
+
const { imageRenderingSystem } = phx.frontend.useConfigurator()
|
|
103
|
+
}
|
|
103
104
|
```
|
|
104
105
|
|
|
105
106
|
### State Subscriptions
|
|
@@ -209,7 +210,7 @@ const validity = phx.frontend.useStateValidity()
|
|
|
209
210
|
const controlViolations = phx.frontend.useControlViolations(entityId, controlId)
|
|
210
211
|
|
|
211
212
|
// Finalize violations
|
|
212
|
-
const {
|
|
213
|
+
const { disabled, hints } = phx.frontend.useFinalizeViolations()
|
|
213
214
|
|
|
214
215
|
// Map violations to hints
|
|
215
216
|
const mapViolationHints = phx.frontend.useMapViolationHints()
|
|
@@ -284,89 +285,89 @@ The interface provides factory functions for creating handlers and providers:
|
|
|
284
285
|
|
|
285
286
|
```typescript
|
|
286
287
|
// Runs early in app startup, before session auth
|
|
287
|
-
const OnApplicationStart = phx.frontend.createOnApplicationStartProvider(({
|
|
288
|
-
return <YourProvider>{
|
|
289
|
-
|
|
288
|
+
const OnApplicationStart = phx.frontend.createOnApplicationStartProvider(({ children }) => {
|
|
289
|
+
return <YourProvider>{children}</YourProvider>
|
|
290
|
+
})
|
|
290
291
|
|
|
291
292
|
// Runs after session auth, before domain data loading
|
|
292
|
-
const OnSessionReady = phx.frontend.createOnSessionReadyProvider(({
|
|
293
|
-
return <YourProvider>{
|
|
294
|
-
|
|
293
|
+
const OnSessionReady = phx.frontend.createOnSessionReadyProvider(({ children }) => {
|
|
294
|
+
return <YourProvider>{children}</YourProvider>
|
|
295
|
+
})
|
|
295
296
|
|
|
296
297
|
// Runs after published data is parsed, before frontend/backend loading
|
|
297
|
-
const OnPublishedDataReady = phx.frontend.createOnPublishedDataReadyProvider(({
|
|
298
|
-
return <YourProvider>{
|
|
299
|
-
|
|
298
|
+
const OnPublishedDataReady = phx.frontend.createOnPublishedDataReadyProvider(({ children }) => {
|
|
299
|
+
return <YourProvider>{children}</YourProvider>
|
|
300
|
+
})
|
|
300
301
|
|
|
301
302
|
// Runs after frontend collection and parsing, before backend loading
|
|
302
|
-
const OnFrontendDataReady = phx.frontend.createOnFrontendDataReadyProvider(({
|
|
303
|
-
return <YourProvider>{
|
|
304
|
-
|
|
303
|
+
const OnFrontendDataReady = phx.frontend.createOnFrontendDataReadyProvider(({ children }) => {
|
|
304
|
+
return <YourProvider>{children}</YourProvider>
|
|
305
|
+
})
|
|
305
306
|
|
|
306
307
|
// Runs after backend loading, before preset selection and configurator initialization
|
|
307
|
-
const OnBackendDataReady = phx.frontend.createOnBackendDataReadyProvider(({
|
|
308
|
-
return <YourProvider>{
|
|
309
|
-
|
|
308
|
+
const OnBackendDataReady = phx.frontend.createOnBackendDataReadyProvider(({ children }) => {
|
|
309
|
+
return <YourProvider>{children}</YourProvider>
|
|
310
|
+
})
|
|
310
311
|
|
|
311
312
|
// Runs after routing and preset selection are ready
|
|
312
|
-
const OnRoutingReady = phx.frontend.createOnRoutingReadyProvider(({
|
|
313
|
-
return <YourProvider>{
|
|
314
|
-
|
|
313
|
+
const OnRoutingReady = phx.frontend.createOnRoutingReadyProvider(({ children }) => {
|
|
314
|
+
return <YourProvider>{children}</YourProvider>
|
|
315
|
+
})
|
|
315
316
|
|
|
316
317
|
// Wraps OwnerUi and ReadUi pages
|
|
317
|
-
const OnConfiguratorReady = phx.frontend.createOnConfiguratorReadyProvider(({
|
|
318
|
-
return <YourProvider>{
|
|
319
|
-
|
|
318
|
+
const OnConfiguratorReady = phx.frontend.createOnConfiguratorReadyProvider(({ children }) => {
|
|
319
|
+
return <YourProvider>{children}</YourProvider>
|
|
320
|
+
})
|
|
320
321
|
```
|
|
321
322
|
|
|
322
323
|
### Data Handlers
|
|
323
324
|
|
|
324
325
|
```typescript
|
|
325
326
|
// Collect frontend data during initialization
|
|
326
|
-
const collectFrontendData = phx.frontend.createCollectFrontendDataHandler(async
|
|
327
|
-
return {
|
|
328
|
-
|
|
327
|
+
const collectFrontendData = phx.frontend.createCollectFrontendDataHandler(async context => {
|
|
328
|
+
return { customData: await fetchCustomData() }
|
|
329
|
+
})
|
|
329
330
|
|
|
330
331
|
// Override which published chunks are fetched by the frontend.
|
|
331
332
|
// See Publishing page for full documentation.
|
|
332
|
-
const filterPublishedChunks = phx.frontend.createFilterPublishedChunksHandler(() => {
|
|
333
|
+
const filterPublishedChunks = phx.frontend.createFilterPublishedChunksHandler(() => {
|
|
333
334
|
const session = phx.frontend.useSession()
|
|
334
335
|
|
|
335
|
-
return useMemo(() => {
|
|
336
|
+
return useMemo(() => {
|
|
336
337
|
if (session.sessionPayload?.role === 'admin') return ['styling', 'catalog-na']
|
|
337
338
|
return ['styling']
|
|
338
|
-
|
|
339
|
-
|
|
339
|
+
}, [session.sessionPayload?.role])
|
|
340
|
+
})
|
|
340
341
|
|
|
341
342
|
// Runs after preset data is loaded
|
|
342
|
-
const onPresetLoaded = phx.frontend.createOnPresetLoadedHandler((entities, presetData) => {
|
|
343
|
+
const onPresetLoaded = phx.frontend.createOnPresetLoadedHandler((entities, presetData) => {
|
|
343
344
|
// Can modify preset entities before rendering
|
|
344
345
|
return entities
|
|
345
|
-
|
|
346
|
+
})
|
|
346
347
|
|
|
347
348
|
// Intercept navigation to index/presets page
|
|
348
|
-
const onGoToIndex = phx.frontend.createOnGoToIndexHandler(() => {
|
|
349
|
+
const onGoToIndex = phx.frontend.createOnGoToIndexHandler(() => {
|
|
349
350
|
// Custom logic before navigating
|
|
350
|
-
|
|
351
|
+
})
|
|
351
352
|
```
|
|
352
353
|
|
|
353
354
|
### UI Components
|
|
354
355
|
|
|
355
356
|
```typescript
|
|
356
357
|
// Custom Phoenix layout element
|
|
357
|
-
const MyLayoutElement = phx.frontend.createCustomPhoenixLayoutElement(() => {
|
|
358
|
+
const MyLayoutElement = phx.frontend.createCustomPhoenixLayoutElement('MyLayoutElement', () => {
|
|
358
359
|
return <div>Custom Layout</div>
|
|
359
|
-
|
|
360
|
+
})
|
|
360
361
|
|
|
361
362
|
// Custom Phoenix entity element
|
|
362
|
-
const MyEntityElement = phx.frontend.createCustomPhoenixEntityElement(() => {
|
|
363
|
+
const MyEntityElement = phx.frontend.createCustomPhoenixEntityElement('MyEntityElement', () => {
|
|
363
364
|
return <mesh>...</mesh>
|
|
364
|
-
|
|
365
|
+
})
|
|
365
366
|
|
|
366
367
|
// Custom page when auth fails
|
|
367
|
-
const NotAuthenticatedPage = phx.frontend.createNotAuthenticatedPage(() => {
|
|
368
|
+
const NotAuthenticatedPage = phx.frontend.createNotAuthenticatedPage(() => {
|
|
368
369
|
return <div>Please log in</div>
|
|
369
|
-
|
|
370
|
+
})
|
|
370
371
|
```
|
|
371
372
|
|
|
372
373
|
### Preset UI
|
|
@@ -383,21 +384,55 @@ const PresetAction = phx.frontend.createPresetPageAction(PresetHelpAction)
|
|
|
383
384
|
const PresetOverlay = phx.frontend.createPresetDeviceNotSupported(SmallScreenNotice)
|
|
384
385
|
```
|
|
385
386
|
|
|
387
|
+
### Loaded Configuration UI
|
|
388
|
+
|
|
389
|
+
A layout element registered under the name `ConfiguratorLayout` replaces the whole UI of a loaded configuration, in owner and in read mode, whatever `layout` the spec sets. Without it, Phoenix renders the layout the spec selects.
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
// src/elements/ConfiguratorLayout.tsx
|
|
393
|
+
import { LayoutSlots, useSceneSafeArea } from '@salsita-ai/sdk/configurator'
|
|
394
|
+
|
|
395
|
+
const SIDEBAR_WIDTH = 320
|
|
396
|
+
|
|
397
|
+
export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout', () => {
|
|
398
|
+
const { containerRef, safeAreaRef } = useSceneSafeArea()
|
|
399
|
+
|
|
400
|
+
return (
|
|
401
|
+
<div ref={containerRef} style={{ position: 'absolute', inset: 0 }}>
|
|
402
|
+
<LayoutSlots.Services />
|
|
403
|
+
<LayoutSlots.SharedCanvas />
|
|
404
|
+
<aside style={{ position: 'absolute', insetBlock: 0, insetInlineStart: 0, inlineSize: SIDEBAR_WIDTH }}>
|
|
405
|
+
Project sidebar
|
|
406
|
+
</aside>
|
|
407
|
+
<div
|
|
408
|
+
ref={safeAreaRef}
|
|
409
|
+
style={{ position: 'absolute', insetBlock: 0, insetInlineStart: SIDEBAR_WIDTH, insetInlineEnd: 0, pointerEvents: 'none' }}
|
|
410
|
+
/>
|
|
411
|
+
</div>
|
|
412
|
+
)
|
|
413
|
+
})
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
- `LayoutSlots.SharedCanvas` renders the scene. It is absolutely positioned and fills its nearest positioned ancestor, so the layout's root is positioned and covers the page. The layout positions its own UI above the canvas and after it in the markup, as the `aside` does; unpositioned content is painted under the canvas.
|
|
417
|
+
- `useSceneSafeArea()`, called without arguments, tells the camera which part of the canvas the UI leaves free. Attach `containerRef` to the element that covers the whole canvas and `safeAreaRef` to the region the scene should stay visible in: the hook measures the gap between the two and the camera frames the scene inside the safe area. Without it the scene is framed in the whole canvas. The safe-area element lies over the canvas, so it sets `pointerEvents: 'none'` to leave orbiting and clicking to the scene. A `canvasPadding` component in the spec scales the measured gaps, see [Canvas Padding](?path=/docs/layouts-canvas-padding--docs).
|
|
418
|
+
- The layout has to mount `LayoutSlots.SharedCanvas`, or the project sets `appLoader: legacy`. Otherwise the app loader stays on screen.
|
|
419
|
+
- The layout composes the rest from what `@salsita-ai/sdk/configurator` exports: `LayoutSlots.Services` and the other members of `LayoutSlots`.
|
|
420
|
+
- Nothing of the built-in layouts is rendered for it. The finalize page is `Legacy.CommonFinalizePage`, the dialog is `LayoutDialog`, and the preview start screen is `DesktopPreview` or `MobilePreview`, which the layout shows while `usePreview().isPreview` is set. Both previews frame the scene themselves, so the layout renders the component that calls `useSceneSafeArea()` only while `isPreview` is not set; it then measures when it mounts after the preview. The assistant panel is not available to a project layout.
|
|
421
|
+
- Any project layout element named `ConfiguratorLayout` takes over the page.
|
|
422
|
+
|
|
386
423
|
## TransformerBuilder
|
|
387
424
|
|
|
388
425
|
Use `transformerBuilder` to create entity transformers:
|
|
389
426
|
|
|
390
427
|
```typescript
|
|
391
428
|
// Create a transformer that runs when specific conditions are met
|
|
392
|
-
const cabinetTransformer = phx.frontend.transformerBuilder
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
{'}'}
|
|
400
|
-
{'}'})
|
|
429
|
+
const cabinetTransformer = phx.frontend.transformerBuilder.when(Cabinet.is).do((cabinet, context) => {
|
|
430
|
+
// Transform cabinet data
|
|
431
|
+
return {
|
|
432
|
+
...cabinet,
|
|
433
|
+
$computedField: calculateSomething(cabinet),
|
|
434
|
+
}
|
|
435
|
+
})
|
|
401
436
|
```
|
|
402
437
|
|
|
403
438
|
**Note**: The deprecated `createTransformer` function should not be used for new implementations.
|
|
@@ -407,19 +442,19 @@ const cabinetTransformer = phx.frontend.transformerBuilder
|
|
|
407
442
|
The `updates` factory creates type-safe entity updates:
|
|
408
443
|
|
|
409
444
|
```typescript
|
|
410
|
-
const {
|
|
445
|
+
const { storeSystem } = phx.frontend.useDomain()
|
|
411
446
|
|
|
412
447
|
// Create new entity
|
|
413
|
-
const createUpdates = phx.frontend.updates.create({
|
|
448
|
+
const createUpdates = phx.frontend.updates.create({
|
|
414
449
|
type: 'Cabinet',
|
|
415
450
|
width: 800,
|
|
416
451
|
height: 2000,
|
|
417
|
-
|
|
452
|
+
})
|
|
418
453
|
|
|
419
454
|
// Modify existing entity
|
|
420
|
-
const modifyUpdates = phx.frontend.updates.modify(cabinetId, {
|
|
455
|
+
const modifyUpdates = phx.frontend.updates.modify(cabinetId, {
|
|
421
456
|
width: 1000,
|
|
422
|
-
|
|
457
|
+
})
|
|
423
458
|
|
|
424
459
|
// Delete entity
|
|
425
460
|
const deleteUpdates = phx.frontend.updates.delete(cabinetId)
|
|
@@ -29,10 +29,11 @@ pnpm phoenix dev
|
|
|
29
29
|
|
|
30
30
|
1. Refuses to run when a `spec/` folder already exists, so it never touches an existing project.
|
|
31
31
|
2. Writes a `package.json` that depends on the CLI's SDK version when the folder has none. An existing `package.json` is kept, as long as it depends on the SDK.
|
|
32
|
-
3.
|
|
33
|
-
4. Writes `
|
|
32
|
+
3. Adds the `dev`, `build`, `deploy`, `check` and `fix` scripts to `package.json` (see [Project Scripts](#project-scripts)). A script the project already has under one of those names keeps its command.
|
|
33
|
+
4. Writes a starter spec (`spec/application.yaml`, `spec/configurator.yaml`) with one resizable, recolorable box, and switches `package.json` to the application-spec pipeline.
|
|
34
|
+
5. Writes `pnpm-workspace.yaml` with the pnpm settings the first install needs: the build scripts of the SDK's dependencies are allowed or denied, and `@salsita-ai/*` packages are exempt from pnpm's minimum release age. A project inside a pnpm monorepo gets no file; the workspace root holds these settings.
|
|
34
35
|
|
|
35
|
-
It asks nothing, writes no `.env` and contacts no server. `phoenix sync` then generates every other project file, including the agent skills described in [Project Structure](?path=/docs/phoenix-project-structure--docs).
|
|
36
|
+
It asks nothing, writes no `.env` and contacts no server. `phoenix sync` then generates every other project file, including the agent skills described in [Project Structure](?path=/docs/phoenix-project-structure--docs). The `.gitignore` is among them only when the folder is inside a git repository, so run `git init` before the first sync, or sync again after it.
|
|
36
37
|
|
|
37
38
|
### Which project `phoenix dev` develops in
|
|
38
39
|
|
|
@@ -114,8 +115,6 @@ For quick local development without a server, use:
|
|
|
114
115
|
|
|
115
116
|
```bash
|
|
116
117
|
phoenix dev --persistence local-storage
|
|
117
|
-
# or use the shortcut script:
|
|
118
|
-
pnpm dev:local
|
|
119
118
|
```
|
|
120
119
|
|
|
121
120
|
**Important:** The server must be running before `deployment:prepare-local` when using `server` persistence mode.
|
|
@@ -158,27 +157,19 @@ spec/application.yaml → Server prepares environment → CLI writes .env.genera
|
|
|
158
157
|
|
|
159
158
|
### Project Scripts
|
|
160
159
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
| Script
|
|
164
|
-
|
|
|
165
|
-
| `dev`
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
| `prettify:fix` | `prettier --write .` | Prettier fix only |
|
|
175
|
-
| `verify:fix` | `phoenix build && phoenix fix && phoenix check --types` | Build, fix, then type-check |
|
|
176
|
-
| `test` | `vitest --passWithNoTests run` | Run tests |
|
|
177
|
-
| `test:watch` | `vitest` | Tests in watch mode |
|
|
178
|
-
| `prepare:ci` | `phoenix prepare:ci` | CI environment setup |
|
|
179
|
-
| `deployment:prepare-local` | `phoenix deployment:prepare-local` | Prepare local environment |
|
|
180
|
-
|
|
181
|
-
Custom scripts you add to `package.json` are preserved during sync. Deprecated scripts (like `environment:create`, `deployment:register`) are automatically removed.
|
|
160
|
+
`phoenix init` writes five scripts into `package.json`, each a shortcut for the CLI command of the same name:
|
|
161
|
+
|
|
162
|
+
| Script | Command | Purpose |
|
|
163
|
+
| -------- | ---------------- | --------------------------------------------- |
|
|
164
|
+
| `dev` | `phoenix dev` | Dev server with hot reload |
|
|
165
|
+
| `build` | `phoenix build` | Production build |
|
|
166
|
+
| `deploy` | `phoenix deploy` | Build and publish the project |
|
|
167
|
+
| `check` | `phoenix check` | Run all checks (TypeScript, ESLint, Prettier) |
|
|
168
|
+
| `fix` | `phoenix fix` | Auto-fix lint and formatting issues |
|
|
169
|
+
|
|
170
|
+
The scripts are yours: change, rename or delete them, and add your own. `phoenix sync` and `phoenix clean` leave the `scripts` section of `package.json` as it is, so they add no script, restore none you removed and remove none. Every CLI command also runs without a script, as `pnpm phoenix <command>`.
|
|
171
|
+
|
|
172
|
+
A project may have other scripts than these five, such as `sync`, `test` or `verify:fix`; they are its own and stay as they are. All of this holds for a project on the application-spec pipeline, which `phoenix init` selects. In a project on the legacy pipeline (see [Current and legacy project tooling](#current-and-legacy-project-tooling)), `phoenix sync` and `phoenix clean` write their own set of scripts into `package.json` on every run, over scripts of the same name.
|
|
182
173
|
|
|
183
174
|
### Server-Generated Variables
|
|
184
175
|
|
|
@@ -399,6 +390,8 @@ Removes all build artifacts (`.phoenix/`, `node_modules/`), reinstalls dependenc
|
|
|
399
390
|
|
|
400
391
|
Syncs project dependencies and regenerates all project files without building Vite bundles or webhooks. Faster than `clean` — use this for routine updates after changing spec files or upgrading dependencies. Before installing, it adds the pnpm settings `phoenix init` writes to a standalone project's `pnpm-workspace.yaml` when they are missing; values you set there are kept.
|
|
401
392
|
|
|
393
|
+
With the application-spec pipeline (see [Current and legacy project tooling](#current-and-legacy-project-tooling)), `.gitignore` is written only when the project is inside a git repository: the project folder or one of its parent folders holds a `.git` entry. Outside a repository, sync neither creates the file nor changes one that is already there, and the same holds for `dev`, `build` and `clean`. Run `git init` before the first sync, or sync again after it, to get the file. The legacy pipeline writes `.gitignore` in every project.
|
|
394
|
+
|
|
402
395
|
| Flag | Env Variable | Default | Description |
|
|
403
396
|
| ---------------- | --------------- | ------- | ------------------------------ |
|
|
404
397
|
| `--force` / `-f` | `FORCE_REBUILD` | `false` | Force full rebuild, skip cache |
|
|
@@ -178,14 +178,14 @@ type CustomMaterial = Phx['AdminModel']['CustomMaterial']
|
|
|
178
178
|
|
|
179
179
|
| File | Auto-Generated? | Notes |
|
|
180
180
|
| ------------------------------------------------------ | --------------- | -------------------------------------- |
|
|
181
|
-
| `package.json` | Partially | Dependencies merged, scripts
|
|
181
|
+
| `package.json` | Partially | Dependencies merged, scripts untouched |
|
|
182
182
|
| `tsconfig.json` | Yes | Managed by Phoenix |
|
|
183
183
|
| `eslint.config.js` | Yes | Managed by Phoenix |
|
|
184
184
|
| `prettier.config.js` | Yes | Managed by Phoenix |
|
|
185
185
|
| `.prettierignore` | Yes | Managed by Phoenix |
|
|
186
186
|
| `.linguirc` | Yes | Managed by Phoenix |
|
|
187
187
|
| `.nvmrc` | Yes | Managed by Phoenix |
|
|
188
|
-
| `.gitignore` | Partially |
|
|
188
|
+
| `.gitignore` | Partially | Only in a git repository; see Markers |
|
|
189
189
|
| `.env` | No | Git-ignored, local config |
|
|
190
190
|
| `.env.example` | No | Template for env vars |
|
|
191
191
|
| `.env.generated` | Yes | Auto-generated IDs, git-ignored |
|
|
@@ -212,17 +212,19 @@ Projects created by earlier SDK versions had this guidance in `CLAUDE.md`, `.cur
|
|
|
212
212
|
|
|
213
213
|
### Markers for Partial Auto-Generation
|
|
214
214
|
|
|
215
|
-
`.gitignore`
|
|
215
|
+
`.gitignore` holds the Phoenix entries between two marker lines. Sync rewrites that block and keeps everything outside it:
|
|
216
216
|
|
|
217
217
|
```gitignore
|
|
218
|
-
#
|
|
218
|
+
# ===== BEGIN PHOENIX AUTO-GENERATED =====
|
|
219
219
|
# ... Phoenix-managed ignores ...
|
|
220
|
-
#
|
|
220
|
+
# ===== END PHOENIX AUTO-GENERATED =====
|
|
221
221
|
|
|
222
|
-
# Your custom ignores
|
|
222
|
+
# Your custom ignores (preserved)
|
|
223
223
|
my-custom-folder/
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
+
With the application-spec pipeline (see [Current and legacy project tooling](?path=/docs/phoenix-phoenix-cli--docs#current-and-legacy-project-tooling)), sync writes `.gitignore` only when the project is inside a git repository: the project folder or one of its parent folders holds a `.git` entry. Outside a repository it neither creates the file nor changes one that is already there. Run `git init` before the first sync, or sync again after it, to get the file. The legacy pipeline writes the file in every project.
|
|
227
|
+
|
|
226
228
|
### Safe to Edit
|
|
227
229
|
|
|
228
230
|
- `src/` (except compiled `src/locales/**/*.ts` files)
|
|
@@ -104,10 +104,10 @@ pnpm install
|
|
|
104
104
|
pnpm phoenix sync
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
`phoenix sync` keeps `spec/` and `src/` as your authored inputs and generates the application shell,
|
|
107
|
+
`phoenix sync` keeps `spec/` and `src/` as your authored inputs and generates the application shell, types, and `.phoenix/` implementation. Start development without a server:
|
|
108
108
|
|
|
109
109
|
```sh
|
|
110
|
-
pnpm dev
|
|
110
|
+
pnpm phoenix dev --persistence local-storage
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
Open the URL printed by the command. The application shell loads with the default `<salsita-configurator>` element and an empty 3D scene.
|
|
@@ -22,7 +22,7 @@ pnpm install
|
|
|
22
22
|
pnpm phoenix sync
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
`phoenix init [folder]` writes the starter project: `package.json`, `pnpm-workspace.yaml` and the starter spec, `spec/application.yaml` and `spec/configurator.yaml`, which describe one box you can resize and recolour. The starter depends on the same SDK version as the CLI that created it. `phoenix sync` then adds the rest of the project:
|
|
25
|
+
`phoenix init [folder]` writes the starter project: `package.json`, `pnpm-workspace.yaml` and the starter spec, `spec/application.yaml` and `spec/configurator.yaml`, which describe one box you can resize and recolour. The starter depends on the same SDK version as the CLI that created it, and its `package.json` has the `dev`, `build`, `deploy`, `check` and `fix` scripts, which are yours to change. `phoenix sync` then adds the rest of the project: webhooks and lint config. It writes a `.gitignore` only when the folder is inside a git repository; run `git init` before the first sync, or sync again after it, to get the file.
|
|
26
26
|
|
|
27
27
|
`init` only writes these files: it asks nothing and contacts no server. It refuses a folder that already has a `spec/` folder. A `package.json` already in the folder is kept, as long as it depends on the SDK.
|
|
28
28
|
|
|
@@ -61,7 +61,7 @@ Signed in, `phoenix dev` needs no `.env` and nothing set up in Studio. When it s
|
|
|
61
61
|
|
|
62
62
|
Every user who signs the CLI in gets their own development environment, so a teammate (a super admin of the workspace, like you) who clones the project and signs in doesn't overwrite your schema or take over your webhooks. The development environment is separate from `production`: nothing you do in `phoenix dev` changes the live configurator.
|
|
63
63
|
|
|
64
|
-
`pnpm dev
|
|
64
|
+
`pnpm phoenix dev --persistence local-storage` still runs the project without any server, with state kept in the browser.
|
|
65
65
|
|
|
66
66
|
Coding agents follow the generated `phoenix` skill: before their first change they start `phoenix dev` and open the preview in your browser, and after `phoenix deploy` starts a build they open its page in Studio.
|
|
67
67
|
|
|
@@ -991,7 +991,7 @@ Save the same OAuth client ID, client secret, and base URL in the customer's **C
|
|
|
991
991
|
JWT_SECRET_BASE64=<base64 of a long random key>
|
|
992
992
|
```
|
|
993
993
|
|
|
994
|
-
For a deployment, run `pnpm phoenix deployment:prepare` followed by `pnpm phoenix deployment:finish`. For local development, run `pnpm deployment:prepare-local`. These current commands use the customer settings stored in Studio; the removed `environment:create` and `deployment:register` commands are not part of the setup flow.
|
|
994
|
+
For a deployment, run `pnpm phoenix deployment:prepare` followed by `pnpm phoenix deployment:finish`. For local development, run `pnpm phoenix deployment:prepare-local`. These current commands use the customer settings stored in Studio; the removed `environment:create` and `deployment:register` commands are not part of the setup flow.
|
|
995
995
|
|
|
996
996
|
#### Clerk organization
|
|
997
997
|
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import type { SessionRole } from '@salsita-ai/contracts/api';
|
|
2
1
|
import { type CustomExpression, type OpenExpressionContext } from '../expression/index.js';
|
|
3
2
|
import type { $DomainEntity, AliasMap, DomainEntity, EntityByType, StateOfComponent, SystemComponentType, SystemComponentTypeConstraint, WritableStateOfComponent } from '../model/index.js';
|
|
4
3
|
import type { DomainState } from '../state/index.js';
|
|
5
4
|
import { type ComponentSystemFilter, type NavigationFilterOptic } from './legacy-filter.js';
|
|
6
|
-
import type {
|
|
7
|
-
|
|
5
|
+
import type { ComponentSystemFilterProps, GradualComponentSystemFilter } from './system-component-filter.js';
|
|
6
|
+
/**
|
|
7
|
+
* The value a project states for one filter criterion: the value `ComponentSystemFilterProps`
|
|
8
|
+
* states for it, with an array read-only. `system-component-filter.ts` holds the same rule for the
|
|
9
|
+
* filter an optic carries; each file keeps its own copy so that the folder exports no new name.
|
|
10
|
+
*/
|
|
11
|
+
type FilterCriterion<KEY extends keyof ComponentSystemFilterProps> = [
|
|
12
|
+
Required<ComponentSystemFilterProps>[KEY]
|
|
13
|
+
] extends [ReadonlyArray<infer ITEM>] ? readonly ITEM[] : Required<ComponentSystemFilterProps>[KEY];
|
|
8
14
|
type CustomOpticRead<ENTITIES extends DomainEntity<string>, $ENTITY extends $DomainEntity<ENTITIES>, COMPONENT_STATE extends Record<string, unknown>, ALLOWED_ROOT_ENTITIES extends ENTITIES, PUBLISHED_DATA extends Record<string, unknown>, SESSION_DATA extends Record<string, unknown>, FRONTEND_DATA extends Record<string, unknown>, CONTEXT_EXTENSION> = (self: $ENTITY, state: DomainState<ENTITIES>, ctx: OpenExpressionContext<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, CONTEXT_EXTENSION>) => COMPONENT_STATE;
|
|
9
15
|
type CustomOpticAttach<ENTITIES extends DomainEntity<string>, ALLOWED_ROOT_ENTITIES extends ENTITIES, PUBLISHED_DATA extends Record<string, unknown>, SESSION_DATA extends Record<string, unknown>, FRONTEND_DATA extends Record<string, unknown>, ENTITY extends ENTITIES, $ENTITY extends $DomainEntity<ENTITIES>, COMPONENT_TYPE extends SystemComponentTypeConstraint, CONTEXT_EXTENSION> = (read: CustomOpticRead<ENTITIES, $ENTITY, StateOfComponent<COMPONENT_TYPE>, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, CONTEXT_EXTENSION>, write?: DomainComponentSimpleOptic<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITY, COMPONENT_TYPE, CONTEXT_EXTENSION>['write']) => DomainComponentSimpleOptic<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ENTITY, COMPONENT_TYPE, CONTEXT_EXTENSION>;
|
|
10
16
|
export interface DomainComponentSimpleOptic<ENTITIES extends DomainEntity<string>, ALLOWED_ROOT_ENTITIES extends ENTITIES, PUBLISHED_DATA extends Record<string, unknown>, SESSION_DATA extends Record<string, unknown>, FRONTEND_DATA extends Record<string, unknown>, ENTITY extends ENTITIES, COMPONENT_TYPE extends SystemComponentTypeConstraint, CONTEXT_EXTENSION = unknown> {
|
|
@@ -48,20 +54,7 @@ export declare class DomainComponentOpticBuilder<ENTITIES extends DomainEntity<s
|
|
|
48
54
|
*/
|
|
49
55
|
withFilter(filter: ComponentSystemFilter | NavigationFilterOptic<ENTITIES, $ENTITY>): DomainComponentOpticBuilder<ENTITIES, ALLOWED_ROOT_ENTITIES, $MAP, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ALIASES, ENTITY, $ENTITY, COMPONENT_TYPE, CONTEXT_EXTENSION>;
|
|
50
56
|
withGradualFilter(filter: {
|
|
51
|
-
readonly
|
|
52
|
-
readonly selectedEntityIds?: readonly IdOrWithId[] | IdOrWithId | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, readonly IdOrWithId[] | IdOrWithId | undefined, CONTEXT_EXTENSION>;
|
|
53
|
-
readonly hoveredEntityIds?: readonly IdOrWithId[] | IdOrWithId | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, readonly IdOrWithId[] | IdOrWithId | undefined, CONTEXT_EXTENSION>;
|
|
54
|
-
readonly draggedEntityIds?: readonly IdOrWithId[] | IdOrWithId | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, readonly IdOrWithId[] | IdOrWithId | undefined, CONTEXT_EXTENSION>;
|
|
55
|
-
readonly exportedEntityIds?: readonly IdOrWithId[] | IdOrWithId | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, readonly IdOrWithId[] | IdOrWithId | undefined, CONTEXT_EXTENSION>;
|
|
56
|
-
readonly viewStates?: readonly string[] | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, readonly string[] | undefined, CONTEXT_EXTENSION>;
|
|
57
|
-
readonly toolStates?: readonly string[] | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, readonly string[] | undefined, CONTEXT_EXTENSION>;
|
|
58
|
-
readonly exportStates?: readonly string[] | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, readonly string[] | undefined, CONTEXT_EXTENSION>;
|
|
59
|
-
readonly modes?: ReadonlyArray<'owner' | 'read' | 'finalize'> | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, ReadonlyArray<'owner' | 'read' | 'finalize'> | undefined, CONTEXT_EXTENSION>;
|
|
60
|
-
readonly roles?: readonly SessionRole[] | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, readonly SessionRole[] | undefined, CONTEXT_EXTENSION>;
|
|
61
|
-
readonly active?: boolean | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, boolean | undefined, CONTEXT_EXTENSION>;
|
|
62
|
-
readonly fullscreen?: boolean | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, boolean | undefined, CONTEXT_EXTENSION>;
|
|
63
|
-
readonly debug?: boolean | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, boolean | undefined, CONTEXT_EXTENSION>;
|
|
64
|
-
readonly viewports?: readonly Viewport[] | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, readonly Viewport[] | undefined, CONTEXT_EXTENSION>;
|
|
57
|
+
readonly [KEY in keyof ComponentSystemFilterProps]?: FilterCriterion<KEY> | CustomExpression<ENTITIES, ALLOWED_ROOT_ENTITIES, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, $ENTITY, FilterCriterion<KEY> | undefined, CONTEXT_EXTENSION>;
|
|
65
58
|
}): DomainComponentOpticBuilder<ENTITIES, ALLOWED_ROOT_ENTITIES, $MAP, PUBLISHED_DATA, SESSION_DATA, FRONTEND_DATA, ALIASES, ENTITY, $ENTITY, COMPONENT_TYPE, CONTEXT_EXTENSION>;
|
|
66
59
|
private validate;
|
|
67
60
|
private _getProfilerLabel;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createCacheToken as t,getStickyCache as e}from"@salsita-ai/common/utils";import{withProfilerLabel as i}from"../../utils/profiler-label.js";import{createCustomExpression as n}from"../expression/index.js";export class DomainComponentOpticBuilder{aliasMap;entityTypes;componentType;instanceName;navigationFilters;generated;entityTypeOrAliasNames;constructor(t,e,i,n,s=[],a=!1,o=[]){this.aliasMap=t,this.entityTypes=e,this.componentType=i,this.instanceName=n,this.navigationFilters=s,this.generated=a,this.entityTypeOrAliasNames=o}static from(t){return new DomainComponentOpticBuilder(t,[])}withEntity(t){return new DomainComponentOpticBuilder(this.aliasMap,[...this.entityTypes,t],this.componentType,this.instanceName,this.navigationFilters,this.generated,[...this.entityTypeOrAliasNames,t])}withAlias(t){return new DomainComponentOpticBuilder(this.aliasMap,[...this.entityTypes,...this.aliasMap[t]],this.componentType,this.instanceName,this.navigationFilters,this.generated,[...this.entityTypeOrAliasNames,String(t)])}withComponent(t,e,i=!1){return new DomainComponentOpticBuilder(this.aliasMap,this.entityTypes,t,e,this.navigationFilters,i,this.entityTypeOrAliasNames)}withFilter(i){if(console.warn(`Component ${this.componentType?.name}#${this.instanceName} on entity ${this.entityTypes.join(", ")} uses a legacy filter with full filter evaluation. This is a suboptimal variant and is deprecated in favor of per-criterion evaluation.
|
|
1
|
+
import{createCacheToken as t,getStickyCache as e}from"@salsita-ai/common/utils";import{withProfilerLabel as i}from"../../utils/profiler-label.js";import{createCustomExpression as n}from"../expression/index.js";let s={navigationStates:void 0,selectedEntityIds:void 0,hoveredEntityIds:void 0,draggedEntityIds:void 0,viewStates:void 0,toolStates:void 0,modes:void 0,roles:void 0,active:void 0,fullscreen:void 0,debug:void 0,viewports:void 0,exportedEntityIds:void 0,exportStates:void 0};export class DomainComponentOpticBuilder{aliasMap;entityTypes;componentType;instanceName;navigationFilters;generated;entityTypeOrAliasNames;constructor(t,e,i,n,s=[],a=!1,o=[]){this.aliasMap=t,this.entityTypes=e,this.componentType=i,this.instanceName=n,this.navigationFilters=s,this.generated=a,this.entityTypeOrAliasNames=o}static from(t){return new DomainComponentOpticBuilder(t,[])}withEntity(t){return new DomainComponentOpticBuilder(this.aliasMap,[...this.entityTypes,t],this.componentType,this.instanceName,this.navigationFilters,this.generated,[...this.entityTypeOrAliasNames,t])}withAlias(t){return new DomainComponentOpticBuilder(this.aliasMap,[...this.entityTypes,...this.aliasMap[t]],this.componentType,this.instanceName,this.navigationFilters,this.generated,[...this.entityTypeOrAliasNames,String(t)])}withComponent(t,e,i=!1){return new DomainComponentOpticBuilder(this.aliasMap,this.entityTypes,t,e,this.navigationFilters,i,this.entityTypeOrAliasNames)}withFilter(i){if(console.warn(`Component ${this.componentType?.name}#${this.instanceName} on entity ${this.entityTypes.join(", ")} uses a legacy filter with full filter evaluation. This is a suboptimal variant and is deprecated in favor of per-criterion evaluation.
|
|
2
2
|
|
|
3
3
|
In your TS files, replace
|
|
4
4
|
withFilter()
|
|
@@ -13,4 +13,4 @@ import{createCacheToken as t,getStickyCache as e}from"@salsita-ai/common/utils";
|
|
|
13
13
|
active:
|
|
14
14
|
$read: $trueOrFalseExpression
|
|
15
15
|
# ... other eventual criteria
|
|
16
|
-
`),"function"==typeof i){let n=t(),
|
|
16
|
+
`),"function"==typeof i){let n=t(),a=t=>({evaluate:(s,a,o)=>{let r=e(a,n),l=r.get(s);if(!l){if(void 0===(l=i(o.domain.get$Entity(s,a),a)))throw Error(`Dynamic filter on component ${this.componentType?.name}.${this.instanceName} on entity ${s} returned 'undefined'. This is likely a bug in the component definition, check whether you are referencing a correct expression or field!`);r.put(s,l)}return"boolean"==typeof l?"active"===t?l:void 0:l[t]},meta:{}});return new DomainComponentOpticBuilder(this.aliasMap,this.entityTypes,this.componentType,this.instanceName,[...this.navigationFilters,{...s,navigationStates:a("navigationStates"),selectedEntityIds:a("selectedEntityIds"),hoveredEntityIds:a("hoveredEntityIds"),draggedEntityIds:a("draggedEntityIds"),viewStates:a("viewStates"),toolStates:a("toolStates"),modes:a("modes"),roles:a("roles"),active:a("active"),fullscreen:a("fullscreen"),debug:a("debug"),viewports:a("viewports")}],this.generated,this.entityTypeOrAliasNames)}return"boolean"==typeof i?new DomainComponentOpticBuilder(this.aliasMap,this.entityTypes,this.componentType,this.instanceName,[...this.navigationFilters,{...s,active:i}],this.generated,this.entityTypeOrAliasNames):new DomainComponentOpticBuilder(this.aliasMap,this.entityTypes,this.componentType,this.instanceName,[...this.navigationFilters,{...s,...i}],this.generated,this.entityTypeOrAliasNames)}withGradualFilter(t){let e=t=>"function"==typeof t?n(t,{}):t,i={navigationStates:e(t.navigationStates),selectedEntityIds:e(t.selectedEntityIds),hoveredEntityIds:e(t.hoveredEntityIds),draggedEntityIds:e(t.draggedEntityIds),exportedEntityIds:e(t.exportedEntityIds),viewStates:e(t.viewStates),toolStates:e(t.toolStates),exportStates:e(t.exportStates),modes:e(t.modes),roles:e(t.roles),active:e(t.active),fullscreen:e(t.fullscreen),debug:e(t.debug),viewports:e(t.viewports)};return new DomainComponentOpticBuilder(this.aliasMap,this.entityTypes,this.componentType,this.instanceName,[...this.navigationFilters,i],this.generated,this.entityTypeOrAliasNames)}validate(){if(!this.componentType||!this.instanceName)throw Error("Use withComponent before attaching");if(!this.entityTypes.length)throw Error("Use withEntity and/or withAlias before attaching")}_getProfilerLabel(){return[...this.entityTypeOrAliasNames,this.componentType.name,this.instanceName].join("_")}attach=(n,s)=>{var a,o;let r,l;return this.validate(),a={kind:"simple",entityTypes:new Set(this.entityTypes),componentType:this.componentType,instanceName:this.instanceName,navigationFilters:this.navigationFilters,read:(t,e,i)=>n(i.domain.get$Entity(t,e),e,i),write:s??(()=>({})),generated:this.generated},o=this._getProfilerLabel(),r=t(),l=i(o,a.read),{...a,read:(t,i,n)=>{let s=e(i,r),a=s.get(t);if(a)return a;let o=l(t,i,n);return s.put(t,o),o}}};attachMany=n=>{var s,a;let o,r;return this.validate(),s={kind:"dynamic",entityTypes:new Set(this.entityTypes),componentType:this.componentType,instanceName:this.instanceName,navigationFilters:this.navigationFilters,read:(t,e,i)=>n(i.domain.get$Entity(t,e),e,i)},a=this._getProfilerLabel(),o=t(),r=i(a,s.read),{...s,read:(t,i,n)=>{let s=e(i,o),a=s.get(t);if(a)return a;let l=r(t,i,n);return s.put(t,l),l}}}}
|
|
@@ -1,22 +1,7 @@
|
|
|
1
|
-
import type { SessionRole } from '@salsita-ai/contracts/api';
|
|
2
1
|
import type { $DomainEntity, DomainEntity } from '../model/index.js';
|
|
3
2
|
import type { DomainState } from '../state/index.js';
|
|
4
|
-
import { type
|
|
5
|
-
|
|
6
|
-
export type DeclarativeComponentSystemFilter = {
|
|
7
|
-
navigationStates?: string[];
|
|
8
|
-
selectedEntityIds?: readonly IdOrWithId[] | IdOrWithId;
|
|
9
|
-
hoveredEntityIds?: readonly IdOrWithId[] | IdOrWithId;
|
|
10
|
-
draggedEntityIds?: readonly IdOrWithId[] | IdOrWithId;
|
|
11
|
-
viewStates?: string[];
|
|
12
|
-
toolStates?: string[];
|
|
13
|
-
modes?: Array<'owner' | 'read' | 'finalize'>;
|
|
14
|
-
roles?: SessionRole[];
|
|
15
|
-
active?: boolean;
|
|
16
|
-
fullscreen?: boolean;
|
|
17
|
-
debug?: boolean;
|
|
18
|
-
viewports?: readonly Viewport[];
|
|
19
|
-
};
|
|
3
|
+
import { type ComponentSystemFilterProps } from './system-component-filter.js';
|
|
4
|
+
export type DeclarativeComponentSystemFilter = Omit<ComponentSystemFilterProps, 'exportedEntityIds' | 'exportStates'>;
|
|
20
5
|
export type ComponentSystemFilter = DeclarativeComponentSystemFilter | boolean;
|
|
21
6
|
export declare const ComponentSystemFilterSchema: {
|
|
22
7
|
readonly anyOf: [{
|