@salsita-ai/sdk 0.50.0-develop.91d65bd507 → 0.50.0-develop.9239ced550
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 +1 -1
- package/dist/application/index.d.ts +1 -0
- package/dist/application/index.js +1 -1
- package/dist/application/mount-application.d.ts +1 -1
- package/dist/application/mount-application.js +1 -1
- package/dist/application/mount-configurator.d.ts +19 -0
- package/dist/application/mount-configurator.js +1 -0
- package/dist/application/mounted-application-runtime.d.ts +4 -0
- package/dist/application/mounted-application-runtime.js +1 -0
- package/dist/application/register-web-component.d.ts +2 -0
- package/dist/application/register-web-component.js +1 -0
- package/dist/application-spec/core/enhanced-json-value.d.ts +2 -31
- package/dist/application-spec/core/enhanced-json-value.js +1 -1
- package/dist/application-spec/core/index.d.ts +1 -3
- package/dist/application-spec/core/index.js +1 -1
- package/dist/application-spec/core/markers/index.d.ts +1 -1
- package/dist/application-spec/core/markers/index.js +1 -1
- package/dist/application-spec/core/markers/msg-marker.d.ts +2 -28
- package/dist/application-spec/core/markers/msg-marker.js +1 -1
- package/dist/application-spec/core/markers/raw-eval-write-marker.d.ts +1 -1
- package/dist/application-spec/core/markers/raw-eval-write-marker.js +1 -1
- package/dist/application-spec/core/markers/raw-read-write-marker.js +1 -1
- package/dist/application-spec/generate-application/code/project-scaffolding.js +2 -2
- package/dist/application-spec/generate-application/code/templates/index.d.ts +2 -6
- package/dist/application-spec/generate-application/code/templates/index.js +1 -1
- package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill-bundle.d.ts +10 -0
- package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill-bundle.js +7 -0
- package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill.js +1 -1
- package/dist/application-spec/generate-application/implementations/phx-application-types.d.ts +25 -0
- package/dist/application-spec/generate-application/implementations/phx-application-types.js +1 -0
- package/dist/application-spec/generate-application/implementations/phx-domain.d.ts +36 -0
- package/dist/application-spec/generate-application/implementations/phx-domain.js +1 -0
- package/dist/application-spec/generate-application/implementations/phx-model.d.ts +24 -0
- package/dist/application-spec/generate-application/implementations/phx-model.js +1 -0
- package/dist/application-spec/generate-application/implementations/phx.d.ts +22 -0
- package/dist/application-spec/generate-application/implementations/phx.js +1 -0
- package/dist/application-spec/generate-application/index.d.ts +2 -0
- package/dist/application-spec/generate-application/index.js +1 -1
- package/dist/application-spec/generate-application/runtime/admin-runtime/admin-model-runtime.d.ts +3 -3
- package/dist/application-spec/generate-application/runtime/admin-runtime/admin-runtime.d.ts +4 -2
- package/dist/application-spec/generate-application/runtime/admin-runtime/admin-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/admin-runtime/admin-spec-runtime.d.ts +16 -0
- package/dist/application-spec/generate-application/runtime/admin-runtime/admin-spec-runtime.js +1 -0
- package/dist/application-spec/generate-application/runtime/application-runtime.d.ts +5 -0
- package/dist/application-spec/generate-application/runtime/application-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/application-spec-slice.d.ts +71 -0
- package/dist/application-spec/generate-application/runtime/application-spec-slice.js +1 -0
- package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-model-runtime.d.ts +3 -3
- package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-runtime.d.ts +4 -2
- package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-spec-runtime.d.ts +16 -0
- package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-spec-runtime.js +1 -0
- package/dist/application-spec/generate-application/runtime/create-application.d.ts +57 -0
- package/dist/application-spec/generate-application/runtime/create-application.js +1 -0
- package/dist/application-spec/generate-application/runtime/factory-data/recipe-defaults.js +1 -1
- package/dist/application-spec/generate-application/runtime/factory-data/static-violations.d.ts +1 -1
- package/dist/application-spec/generate-application/runtime/factory-data/static-violations.js +1 -1
- package/dist/application-spec/generate-application/runtime/generate-runtime-instance.d.ts +1 -1
- package/dist/application-spec/generate-application/runtime/generate-runtime-instance.js +1 -1
- package/dist/application-spec/generate-application/runtime/index.d.ts +8 -0
- package/dist/application-spec/generate-application/runtime/index.js +1 -0
- package/dist/application-spec/generate-application/runtime/sections/build-sdk-optic.js +1 -1
- package/dist/application-spec/generate-application/runtime/sections/expression-runtime.d.ts +1 -1
- package/dist/application-spec/generate-application/runtime/sections/expression-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/sections/generate-memo-eval-plan-runtime.d.ts +1 -1
- package/dist/application-spec/generate-application/runtime/sections/generate-memo-eval-plan-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/spec-code/resolve-spec-code.d.ts +1 -1
- package/dist/application-spec/generate-application/runtime/spec-runtime.d.ts +21 -0
- package/dist/application-spec/generate-application/runtime/spec-runtime.js +1 -0
- package/dist/application-spec/generate-application/runtime-spec/encoded-runtime-spec.d.ts +3 -2
- package/dist/application-spec/generate-application/runtime-spec/runtime-spec.d.ts +44 -9
- package/dist/application-spec/generate-application/runtime-spec/runtime-spec.js +1 -1
- package/dist/application-spec/parse-application-files/raw-spec/raw-expressions.d.ts +1 -1
- package/dist/application-spec/parse-application-files/raw-spec/raw-expressions.js +1 -1
- package/dist/client/authorized-client.d.ts +3 -1
- package/dist/client/authorized-client.js +1 -1
- package/dist/client/contract.d.ts +17 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/local-authorized-client.d.ts +3 -1
- package/dist/client/local-authorized-client.js +1 -1
- package/dist/client/memory-authorized-client.d.ts +4 -1
- package/dist/client/memory-authorized-client.js +1 -1
- package/dist/client/phoenix-api.d.ts +228 -0
- package/dist/configurator/ConfiguratorApp.js +1 -1
- package/dist/configurator/application-runtime/extensions/selectors.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.d.ts +13 -0
- package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/assistant-system/graphql-tools.d.ts +2 -1
- package/dist/configurator/application-runtime/extensions/systems/assistant-system/graphql-tools.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.d.ts +15 -9
- package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.js +1 -1
- package/dist/configurator/ui/assistant/screenshots/ScreenshotThumbnails.d.ts +1 -1
- package/dist/configurator/ui/assistant/screenshots/ScreenshotThumbnails.js +1 -1
- package/dist/configurator/ui/assistant/screenshots/tool-screenshots.d.ts +12 -4
- package/dist/configurator/ui/assistant/screenshots/tool-screenshots.js +1 -1
- package/dist/configurator/ui/assistant/screenshots/use-screenshot-src.d.ts +8 -0
- package/dist/configurator/ui/assistant/screenshots/use-screenshot-src.js +1 -0
- package/dist/docs/index.json +1 -1
- package/dist/docs/layouts/gallery/nightjar-guitar.mdx +31 -16
- package/dist/docs/layouts/gallery/tidepool-aquascape.mdx +205 -33
- package/dist/docs/phoenix/domain-model.mdx +4 -1
- package/dist/docs/phoenix/phoenix-cli.mdx +14 -6
- package/dist/docs/phoenix/project-structure.mdx +21 -17
- package/dist/docs/phoenix/sdk-builder-workflow.mdx +6 -4
- package/dist/domain/application/assistant/build-assistant-manifest.js +1 -1
- package/dist/domain/application/assistant/generate-assistant-prompt.js +1 -1
- package/dist/domain/application/configurator-builder.d.ts +7 -1
- package/dist/domain/application/configurator-builder.js +1 -1
- package/dist/domain/application/index.d.ts +1 -1
- package/dist/domain/application/index.js +1 -1
- package/dist/domain/application/standard-properties.js +1 -1
- package/dist/domain/domain-runtime/application.d.ts +39 -0
- package/dist/domain/domain-runtime/application.js +1 -0
- package/dist/domain/domain-runtime/domain-runtime.d.ts +36 -4
- package/dist/domain/domain-runtime/domain-runtime.js +1 -1
- package/dist/domain/domain-runtime/environment/initialize.d.ts +1 -1
- package/dist/domain/domain-runtime/index.d.ts +1 -0
- package/dist/domain/domain-runtime/index.js +1 -1
- package/dist/domain/domain-runtime/web-component/actions.d.ts +3 -0
- package/dist/domain/domain-runtime/web-component/actions.js +1 -1
- package/dist/domain/domain-runtime/web-component/initialize.d.ts +1 -0
- package/dist/domain/domain-runtime/web-component/initialize.js +1 -1
- package/dist/domain/domain-runtime/web-component/selectors.d.ts +2 -0
- package/dist/domain/domain-runtime/web-component/selectors.js +1 -1
- package/dist/domain/domain-runtime/web-component/types.d.ts +6 -3
- package/dist/domain/index.d.ts +1 -0
- package/dist/domain/index.js +1 -1
- package/dist/domain/mocks/create-mock-domain-runtime.d.ts +5 -0
- package/dist/domain/spec/application/index.d.ts +1 -0
- package/dist/domain/spec/application/index.js +1 -0
- package/dist/{application-spec/core → domain/spec}/components/component-filter.d.ts +3 -2
- package/dist/domain/spec/enhanced-json-value.d.ts +30 -0
- package/dist/domain/spec/enhanced-json-value.js +1 -0
- package/dist/domain/spec/index.d.ts +5 -0
- package/dist/domain/spec/index.js +1 -0
- package/dist/domain/spec/markers/index.d.ts +2 -0
- package/dist/domain/spec/markers/index.js +1 -0
- package/dist/domain/spec/markers/msg-marker.d.ts +28 -0
- package/dist/domain/spec/markers/msg-marker.js +1 -0
- package/dist/editor/domain-editors/DomainSpecContext.js +1 -1
- package/dist/editor/domain-editors/apply-spec-update.d.ts +2 -1
- package/dist/editor/domain-editors/apply-spec-update.js +1 -1
- package/dist/editor/domain-editors/use-implementation-files.d.ts +0 -3
- package/dist/editor/domain-editors/use-implementation-files.js +1 -1
- package/dist/phoenix/application-spec/generate-application-spec-project.js +2 -2
- package/dist/phoenix/application-spec/resolve-deployment-data.d.ts +11 -0
- package/dist/phoenix/application-spec/resolve-deployment-data.js +2 -2
- package/dist/phoenix/bin.js +1 -1
- package/dist/phoenix/cli.js +1 -1
- package/dist/phoenix/commands/deploy.js +1 -1
- package/dist/phoenix/commands/dev.js +5 -5
- package/dist/phoenix/commands/generated-env-markers.d.ts +1 -0
- package/dist/phoenix/commands/generated-env-markers.js +1 -1
- package/dist/phoenix/commands/index.d.ts +1 -1
- package/dist/phoenix/commands/index.js +1 -1
- package/dist/phoenix/lingui/index.d.ts +1 -0
- package/dist/phoenix/lingui/index.js +1 -0
- package/dist/phoenix/{services → lingui}/lingui.d.ts +5 -2
- package/dist/phoenix/lingui/lingui.js +1 -0
- package/dist/phoenix/services/application-spec-project-watcher.d.ts +3 -1
- package/dist/phoenix/services/application-spec-project-watcher.js +2 -2
- package/dist/phoenix/services/index.d.ts +1 -0
- package/dist/phoenix/services/index.js +1 -1
- package/dist/phoenix/services/lingui-watcher.js +1 -1
- package/dist/phoenix/services/prepare-development-environment.js +1 -1
- package/dist/phoenix/services/vite-config.js +1 -1
- package/dist/react-sdk/application-runtime/common-services/analytics/analytics-service.d.ts +1 -1
- package/dist/react-sdk/application-runtime/common-services/initialize.d.ts +2 -2
- package/dist/react-sdk/application-runtime/hooks/use-access-service.js +1 -1
- package/dist/react-sdk/application-runtime/hooks/use-web-component-readonly-sync.d.ts +4 -11
- package/dist/react-sdk/application-runtime/hooks/use-web-component-readonly-sync.js +1 -1
- package/dist/react-sdk/application-runtime/hooks/use-web-component.d.ts +5 -0
- package/dist/react-sdk/application-runtime/hooks/use-web-component.js +1 -1
- package/dist/react-sdk/application-runtime/host-properties/actions.d.ts +15 -0
- package/dist/react-sdk/application-runtime/host-properties/actions.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/connect-host-properties.d.ts +30 -0
- package/dist/react-sdk/application-runtime/host-properties/connect-host-properties.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/domain-host-properties.d.ts +7 -0
- package/dist/react-sdk/application-runtime/host-properties/domain-host-properties.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/host-property-descriptors.d.ts +21 -0
- package/dist/react-sdk/application-runtime/host-properties/host-property-descriptors.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/host-property-scopes.d.ts +42 -0
- package/dist/react-sdk/application-runtime/host-properties/host-property-scopes.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/index.d.ts +8 -0
- package/dist/react-sdk/application-runtime/host-properties/index.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/initialize.d.ts +7 -0
- package/dist/react-sdk/application-runtime/host-properties/initialize.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/selectors.d.ts +54 -0
- package/dist/react-sdk/application-runtime/host-properties/selectors.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/types.d.ts +18 -0
- package/dist/react-sdk/application-runtime/host-properties/types.js +1 -0
- package/dist/react-sdk/application-runtime/index.d.ts +1 -0
- package/dist/react-sdk/application-runtime/mocks/create-mock-application-runtime.js +1 -1
- package/dist/react-sdk/application-runtime/runtime/create-application-runtime.d.ts +151 -9
- package/dist/react-sdk/application-runtime/runtime/create-application-runtime.js +1 -1
- package/package.json +3 -3
- package/dist/phoenix/services/lingui.js +0 -1
- /package/dist/{application-spec/core → domain/spec/application}/application-instance.d.ts +0 -0
- /package/dist/{application-spec/core → domain/spec/application}/application-instance.js +0 -0
- /package/dist/{application-spec/core → domain/spec}/components/component-filter.js +0 -0
- /package/dist/{application-spec/core → domain/spec}/components/index.d.ts +0 -0
- /package/dist/{application-spec/core → domain/spec}/components/index.js +0 -0
- /package/dist/{application-spec/core → domain/spec}/components/sdk-code-ref.d.ts +0 -0
- /package/dist/{application-spec/core → domain/spec}/components/sdk-code-ref.js +0 -0
- /package/dist/{application-spec/core → domain/spec}/expressions/index.d.ts +0 -0
- /package/dist/{application-spec/core → domain/spec}/expressions/index.js +0 -0
- /package/dist/{application-spec/core → domain/spec}/expressions/relation-expression-params.d.ts +0 -0
- /package/dist/{application-spec/core → domain/spec}/expressions/relation-expression-params.js +0 -0
- /package/dist/{application-spec/core → domain/spec}/expressions/sdk-expression-implementation.d.ts +0 -0
- /package/dist/{application-spec/core → domain/spec}/expressions/sdk-expression-implementation.js +0 -0
- /package/dist/{application-spec/core → domain/spec}/markers/eval-marker.d.ts +0 -0
- /package/dist/{application-spec/core → domain/spec}/markers/eval-marker.js +0 -0
|
@@ -450,11 +450,13 @@ import { Box, ButtonBase } from '@mui/material'
|
|
|
450
450
|
import { useState } from 'react'
|
|
451
451
|
|
|
452
452
|
import { phx } from '../../.phoenix/phx.js'
|
|
453
|
+
import { announcePinTap } from '../_inspector/index.js'
|
|
453
454
|
import { PieceIcon, TIDEPOOL, pieceLook } from '../_theme/index.js'
|
|
454
455
|
|
|
455
456
|
// Rendered by the spec's `htmlHotspot` above every piece: a pin with the piece's icon, which opens into a label with
|
|
456
457
|
// its name while the pointer is over the piece in the scene or over the pin, and stays open in coral while the piece
|
|
457
|
-
// is selected. A click selects the piece
|
|
458
|
+
// is selected. A click selects the piece and announces the tap, so the phone page opens the inspector even for a piece
|
|
459
|
+
// that is selected already. The click goes no further, so the scene does not take it as a click on empty water.
|
|
458
460
|
export default phx.frontend.createCustomPhoenixEntityElement('DecorLabel', ({ entityId }) => {
|
|
459
461
|
const tank = phx.frontend.useRootEntityState()
|
|
460
462
|
const selectedId = phx.frontend.useSelectedDomainEntityId()
|
|
@@ -480,6 +482,7 @@ export default phx.frontend.createCustomPhoenixEntityElement('DecorLabel', ({ en
|
|
|
480
482
|
onClick={event => {
|
|
481
483
|
event.stopPropagation()
|
|
482
484
|
select(piece.id)
|
|
485
|
+
announcePinTap()
|
|
483
486
|
}}
|
|
484
487
|
onPointerEnter={() => setPointed(true)}
|
|
485
488
|
onPointerLeave={() => setPointed(false)}
|
|
@@ -542,12 +545,45 @@ components:
|
|
|
542
545
|
|
|
543
546
|
```ts
|
|
544
547
|
import { useEffect } from 'react'
|
|
548
|
+
import type { RefObject } from 'react'
|
|
545
549
|
|
|
546
550
|
import { phx } from '../../.phoenix/phx.js'
|
|
547
551
|
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
|
|
552
|
+
// The input types that take text; in these Ctrl+Z belongs to the field
|
|
553
|
+
const TEXT_INPUT_TYPES: ReadonlySet<string> = new Set([
|
|
554
|
+
'text',
|
|
555
|
+
'search',
|
|
556
|
+
'email',
|
|
557
|
+
'url',
|
|
558
|
+
'tel',
|
|
559
|
+
'password',
|
|
560
|
+
'number',
|
|
561
|
+
'date',
|
|
562
|
+
'datetime-local',
|
|
563
|
+
'month',
|
|
564
|
+
'time',
|
|
565
|
+
'week',
|
|
566
|
+
])
|
|
567
|
+
|
|
568
|
+
const isTextEntry = (target: EventTarget | null | undefined) => {
|
|
569
|
+
return (
|
|
570
|
+
target instanceof HTMLTextAreaElement ||
|
|
571
|
+
(target instanceof HTMLInputElement && TEXT_INPUT_TYPES.has(target.type)) ||
|
|
572
|
+
(target instanceof HTMLElement && target.isContentEditable)
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// The configurator sits in a closed shadow root, so to the window a key pressed in it comes from the root's host; the
|
|
577
|
+
// root itself says which of its elements has focus
|
|
578
|
+
const getKeyTarget = (container: HTMLElement, event: KeyboardEvent) => {
|
|
579
|
+
const root = container.getRootNode()
|
|
580
|
+
const [first] = event.composedPath()
|
|
581
|
+
|
|
582
|
+
return root instanceof ShadowRoot && first === root.host ? root.activeElement : first
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Escape puts the selected piece down; Ctrl+Z or ⌘Z undoes, with Shift it redoes
|
|
586
|
+
export const useWorkspaceKeys = (containerRef: RefObject<HTMLElement>) => {
|
|
551
587
|
const tankId = phx.frontend.useRootEntityId()
|
|
552
588
|
const selectedId = phx.frontend.useSelectedDomainEntityId()
|
|
553
589
|
const deselect = phx.frontend.useDeselect()
|
|
@@ -555,10 +591,20 @@ export const useWorkspaceKeys = () => {
|
|
|
555
591
|
|
|
556
592
|
useEffect(() => {
|
|
557
593
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
558
|
-
const
|
|
559
|
-
|
|
594
|
+
const container = containerRef.current
|
|
595
|
+
|
|
596
|
+
if (!container) {
|
|
597
|
+
return
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const target = getKeyTarget(container, event)
|
|
601
|
+
|
|
602
|
+
// Another field's keys are not the layout's; the body's are, as focus falls there when the focused button goes
|
|
603
|
+
if (target !== document.body && !(target instanceof Node && container.contains(target))) {
|
|
604
|
+
return
|
|
605
|
+
}
|
|
560
606
|
|
|
561
|
-
if (event.defaultPrevented ||
|
|
607
|
+
if (event.defaultPrevented || isTextEntry(target)) {
|
|
562
608
|
return
|
|
563
609
|
}
|
|
564
610
|
|
|
@@ -578,10 +624,12 @@ export const useWorkspaceKeys = () => {
|
|
|
578
624
|
window.addEventListener('keydown', onKeyDown)
|
|
579
625
|
|
|
580
626
|
return () => window.removeEventListener('keydown', onKeyDown)
|
|
581
|
-
}, [tankId, selectedId, deselect, undo, redo])
|
|
627
|
+
}, [containerRef, tankId, selectedId, deselect, undo, redo])
|
|
582
628
|
}
|
|
583
629
|
```
|
|
584
630
|
|
|
631
|
+
The listener sits on `window`, but it acts only on keys pressed inside the layout's own container, which `ConfiguratorLayout` passes in as a ref, so another field on the page keeps its own Ctrl+Z, such as the multiline YAML field in the editor's Mocks panel. The configurator sits in a closed shadow root, so the window sees a key pressed in it as coming from the root's host; `getKeyTarget` asks the root which element has focus instead. A key pressed with nothing focused counts too, as its target is the page's `body`: focus falls there when the button just pressed leaves the page, as **Remove**, **Done** and a swap do, and the next Ctrl+Z must still work. Inside the container the hook still leaves a text field alone, whether a `textarea`, a content-editable element or an `input` whose type takes text. The container is focusable (`tabIndex={-1}`), so a click on the scene gives it focus and the shortcuts work after one.
|
|
632
|
+
|
|
585
633
|
## The camera frames the selection
|
|
586
634
|
|
|
587
635
|
The tank has a `fitRootPerspectiveCamera` for each of the spec's two view states, **Front** and **Top**. Every piece has a `fitSelectionPerspectiveCamera` for each too, with a higher `priority`: a selection camera is active only while its entity is selected, so selecting a piece moves the camera to it, and putting it down moves the camera back to the tank. Both follow the view state, with no camera code in the layout.
|
|
@@ -995,7 +1043,8 @@ export const PieceActions = ({ pieceId }: PieceActionsProps) => {
|
|
|
995
1043
|
<Button
|
|
996
1044
|
variant="outlined"
|
|
997
1045
|
aria-expanded={swapOpen}
|
|
998
|
-
|
|
1046
|
+
// The list unmounts while collapsed, so the button points at it only while it is open
|
|
1047
|
+
aria-controls={swapOpen ? 'tidepool-swap' : undefined}
|
|
999
1048
|
onClick={() => setSwapOpen(open => !open)}
|
|
1000
1049
|
startIcon={
|
|
1001
1050
|
<svg viewBox="0 0 24 24" width={18} height={18} fill="none" stroke="currentColor" strokeWidth={1.8}>
|
|
@@ -1206,7 +1255,7 @@ export const Outline = () => {
|
|
|
1206
1255
|
|
|
1207
1256
|
## Undo and redo
|
|
1208
1257
|
|
|
1209
|
-
Every change is one step of the undo history: adding, changing, swapping and removing a piece, and the tank's size and substrate. Selecting a piece is not a change, so undo passes over it; undoing a removal puts the piece back selected. The top bar's buttons read `canUndo` and `canRedo` from `phx.frontend.useUndoRedo()` and call `undo()` and `redo()`, which push the same analytics events as the built-in toolbar buttons; `use-workspace-keys` above binds them to Ctrl+Z and Ctrl+Shift+Z, or ⌘Z and ⇧⌘Z on a Mac. The top bar also shows the tank's name and size, how full it is and the total from `phx.frontend.useRootEntityPrice()`.
|
|
1258
|
+
Every change is one step of the undo history: adding, changing, swapping and removing a piece, and the tank's size and substrate. Selecting a piece is not a change, so undo passes over it; undoing a removal puts the piece back selected. The top bar's buttons read `canUndo` and `canRedo` from `phx.frontend.useUndoRedo()` and call `undo()` and `redo()`, which push the same analytics events as the built-in toolbar buttons; `use-workspace-keys` above binds them to Ctrl+Z and Ctrl+Shift+Z, or ⌘Z and ⇧⌘Z on a Mac, for keys pressed inside the layout or with nothing focused. The top bar also shows the tank's name and size, how full it is and the total from `phx.frontend.useRootEntityPrice()`.
|
|
1210
1259
|
|
|
1211
1260
|
```tsx
|
|
1212
1261
|
import { Box, IconButton, Tooltip, Typography } from '@mui/material'
|
|
@@ -1387,7 +1436,7 @@ export type LatestMessageState = ReturnType<typeof useLatestMessage>
|
|
|
1387
1436
|
|
|
1388
1437
|
## On a phone
|
|
1389
1438
|
|
|
1390
|
-
The same layout in a frame 390 pixels wide, as on a phone. The tank fills the screen, a bar at the bottom offers **Add**, **Inspect** and **Undo**, and the palette and the inspector rise as sheets above it. The safe area ends at the bar, or at the open sheet, so the camera keeps the tank, or the selected piece, in the free space. Selecting a piece, by tapping its pin or by adding it, opens it in the inspector. A pin shows only its icon until its piece is selected, as there is no pointer to open it. Opened on its own from the sidebar, the **Phone** story sets Storybook's viewport to 390 × 844.
|
|
1439
|
+
The same layout in a frame 390 pixels wide, as on a phone. The tank fills the screen, a bar at the bottom offers **Add**, **Inspect** and **Undo**, and the palette and the inspector rise as sheets above it. The safe area ends at the bar, or at the open sheet, so the camera keeps the tank, or the selected piece, in the free space. Selecting a piece, by tapping its pin or by adding it, opens it in the inspector, and tapping the pin of the piece that is still selected opens the inspector again after it was closed. A pin shows only its icon until its piece is selected, as there is no pointer to open it. Opened on its own from the sidebar, the **Phone** story sets Storybook's viewport to 390 × 844.
|
|
1391
1440
|
|
|
1392
1441
|
```tsx
|
|
1393
1442
|
import { Box, ButtonBase, Typography } from '@mui/material'
|
|
@@ -1395,7 +1444,7 @@ import { useEffect, useState } from 'react'
|
|
|
1395
1444
|
import type { ReactNode } from 'react'
|
|
1396
1445
|
|
|
1397
1446
|
import { phx } from '../../.phoenix/phx.js'
|
|
1398
|
-
import { Inspector, Outline } from '../_inspector/index.js'
|
|
1447
|
+
import { Inspector, Outline, onPinTap } from '../_inspector/index.js'
|
|
1399
1448
|
import type { LatestMessageState } from '../_inspector/index.js'
|
|
1400
1449
|
import { BrandMark, CARD_SX, PieceIcon, TIDEPOOL, pieceLook } from '../_theme/index.js'
|
|
1401
1450
|
import { Palette } from './Palette.js'
|
|
@@ -1479,12 +1528,16 @@ export const PhonePage = ({ messages }: PhonePageProps) => {
|
|
|
1479
1528
|
const selected = tank.$decor.find(piece => piece.id === selectedId)
|
|
1480
1529
|
const selectedLook = selected ? pieceLook(selected) : null
|
|
1481
1530
|
|
|
1531
|
+
// Adding a piece selects it, and so does a click on it in the scene; the inspector follows the new selection
|
|
1482
1532
|
useEffect(() => {
|
|
1483
1533
|
if (selectedId !== tankId) {
|
|
1484
1534
|
setSheet('inspect')
|
|
1485
1535
|
}
|
|
1486
1536
|
}, [selectedId, tankId])
|
|
1487
1537
|
|
|
1538
|
+
// A pin tap reopens the inspector for a piece that stayed selected, which the selection above does not show
|
|
1539
|
+
useEffect(() => onPinTap(() => setSheet('inspect')), [])
|
|
1540
|
+
|
|
1488
1541
|
const toggle = (next: Sheet) => setSheet(current => (current === next ? null : next))
|
|
1489
1542
|
|
|
1490
1543
|
return (
|
|
@@ -1600,10 +1653,35 @@ export const PhonePage = ({ messages }: PhonePageProps) => {
|
|
|
1600
1653
|
}
|
|
1601
1654
|
```
|
|
1602
1655
|
|
|
1603
|
-
|
|
1656
|
+
A pin is drawn inside the scene, so it cannot hand the phone page a prop. `DecorLabel` announces each tap through a small channel, and the phone page opens the inspector on it. The channel is what reopens the sheet for a piece that is still selected, where the selection does not change; a piece that is added, or clicked in the scene, opens the inspector through the new selection.
|
|
1657
|
+
|
|
1658
|
+
```ts
|
|
1659
|
+
type PinTapListener = () => void
|
|
1660
|
+
|
|
1661
|
+
const listeners = new Set<PinTapListener>()
|
|
1662
|
+
|
|
1663
|
+
// A pin is drawn inside the scene, so it cannot hand the phone page a prop. It announces each tap here, also the tap
|
|
1664
|
+
// on a piece that is already selected, which the selection alone does not show. The channel is shared by every copy of
|
|
1665
|
+
// the layout in the page; a tap there only opens a sheet, so that costs nothing.
|
|
1666
|
+
export const announcePinTap = () => {
|
|
1667
|
+
listeners.forEach(listener => listener())
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
/** Calls `listener` on every pin tap until the returned function is called. */
|
|
1671
|
+
export const onPinTap = (listener: PinTapListener) => {
|
|
1672
|
+
listeners.add(listener)
|
|
1673
|
+
|
|
1674
|
+
return () => {
|
|
1675
|
+
listeners.delete(listener)
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
```
|
|
1679
|
+
|
|
1680
|
+
`ConfiguratorLayout` keeps the scene mounted and lays the desktop or the phone page over it, by the width of the configurator's container from `phx.frontend.useActiveViewports()`. It also holds the messages and the keyboard shortcuts, so both work whichever page shows; the shortcuts act on keys inside the container it renders, or with nothing focused.
|
|
1604
1681
|
|
|
1605
1682
|
```tsx
|
|
1606
1683
|
import { Box } from '@mui/material'
|
|
1684
|
+
import { useRef } from 'react'
|
|
1607
1685
|
|
|
1608
1686
|
import { LayoutSlots } from '@salsita-ai/sdk/configurator'
|
|
1609
1687
|
|
|
@@ -1615,17 +1693,25 @@ import { useWorkspaceKeys } from '../_shared/use-workspace-keys.js'
|
|
|
1615
1693
|
import { TIDEPOOL } from '../_theme/index.js'
|
|
1616
1694
|
|
|
1617
1695
|
// A workspace over the scene. The canvas stays mounted the whole time; the desktop or the phone page lies over it.
|
|
1618
|
-
// The messages and the keyboard shortcuts live here, so they work whichever page shows and whatever is open.
|
|
1696
|
+
// The messages and the keyboard shortcuts live here, so they work whichever page shows and whatever is open. The
|
|
1697
|
+
// shortcuts act on keys pressed inside this container, or on the body, where focus falls when the focused control goes;
|
|
1698
|
+
// the container takes focus when the scene is clicked, so they work there too.
|
|
1619
1699
|
const ConfiguratorLayout = () => {
|
|
1620
1700
|
// `null` until the container has been measured; only the scene shows until then
|
|
1621
1701
|
const viewports = phx.frontend.useActiveViewports()
|
|
1622
1702
|
const messages = useLatestMessage()
|
|
1623
1703
|
const isDesktop = viewports?.includes('desktop') ?? false
|
|
1704
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
1624
1705
|
|
|
1625
|
-
useWorkspaceKeys()
|
|
1706
|
+
useWorkspaceKeys(containerRef)
|
|
1626
1707
|
|
|
1627
1708
|
return (
|
|
1628
|
-
<Box
|
|
1709
|
+
<Box
|
|
1710
|
+
component="div"
|
|
1711
|
+
ref={containerRef}
|
|
1712
|
+
tabIndex={-1}
|
|
1713
|
+
sx={{ position: 'absolute', inset: 0, overflow: 'clip', bgcolor: TIDEPOOL.foam, outline: 'none' }}
|
|
1714
|
+
>
|
|
1629
1715
|
<LayoutSlots.SharedCanvas />
|
|
1630
1716
|
{viewports !== null && (isDesktop ? <DesktopPage messages={messages} /> : <PhonePage messages={messages} />)}
|
|
1631
1717
|
</Box>
|
|
@@ -1718,16 +1804,18 @@ The aquarium is `public/models/gallery/aquarium.glb`, generated by `scripts/mode
|
|
|
1718
1804
|
- On the neon tetras, the **School size** slider adds and takes away fish two at a time while it moves; at 12 the school costs $42.00.
|
|
1719
1805
|
- **Swap for…** on a stone lists plants, wood and fish; **Amazon sword** puts a green Amazon sword in the stone's place, selected.
|
|
1720
1806
|
- **Remove** takes the piece out, the count drops to "4 / 8" and the inspector shows the tank. **Undo**, or Ctrl+Z, puts the piece back, selected; **Redo**, or Ctrl+Shift+Z, removes it again.
|
|
1721
|
-
- **
|
|
1807
|
+
- Select a piece, press **Remove** and press Ctrl+Z, or ⌘Z on a Mac, at once, with no click in between: the piece comes back, selected. The button just pressed has left the page, so the shortcut must work with nothing focused. The same holds after **Done**, where Ctrl+Z takes back the last change, and after a swap from **Swap for…**, where it puts the first piece back.
|
|
1808
|
+
- **Done**, Escape and a click on empty water put the selected piece down, and the camera returns to the whole tank. Ctrl+Z and Escape work after a click on the tank too.
|
|
1809
|
+
- In the editor's **Mocks** panel, **Create Mock**, **Edit** and a click in a YAML field: typing there and pressing Ctrl+Z undoes the typing; the tank, its total and its undo history stay as they are.
|
|
1722
1810
|
- A row of the palette adds its piece in the first free slot and selects it. With eight pieces in the tank every row is disabled, a banner says "The tank holds 8 pieces at most. Remove a piece to make room.", and "8 / 8" turns coral.
|
|
1723
1811
|
- While more than four pieces stand in the tank, **60 cm** is faded with "A 60 cm tank holds 4 pieces at most." below it. Clicking it shows the same message at the top of the inspector, and the tank keeps its size.
|
|
1724
1812
|
- With four pieces left, **60 cm** shrinks the tank, its pieces close up and the camera frames the smaller tank; the palette is disabled with "A 60 cm tank holds 4 pieces at most. Remove a piece, or choose a larger tank." **120 cm** spreads the pieces out, and the camera frames the larger tank.
|
|
1725
1813
|
- With every piece removed, the outline says "Nothing yet." and the inspector suggests where to start.
|
|
1726
1814
|
- **Top** moves the camera above the tank, also while a piece is selected, and **Front** moves it back; the third button undoes an orbit or a zoom.
|
|
1727
1815
|
- **Aqua soil** and **River gravel** recolour the substrate and change the total.
|
|
1728
|
-
- Applying **Full tank** in the editor's **Mocks** panel opens a full 90 cm tank on aqua soil.
|
|
1729
|
-
- In the **Phone** story the tank fills the screen above a bar with **Add**, **Inspect** and **Undo**. **Add** raises the palette as a sheet, and the camera frames the tank above it; adding a piece opens it in the inspector sheet; tapping a pin opens that piece; closing the sheet gives the tank the screen again.
|
|
1730
|
-
- The browser console shows no errors.
|
|
1816
|
+
- Applying **Full tank** in the editor's **Mocks** panel opens a full 90 cm tank on aqua soil. The canvas system logs three "Force flush timeout" errors to the browser console while it does; opening the story with that template does not.
|
|
1817
|
+
- In the **Phone** story the tank fills the screen above a bar with **Add**, **Inspect** and **Undo**. **Add** raises the palette as a sheet, and the camera frames the tank above it; adding a piece opens it in the inspector sheet; tapping a pin opens that piece; closing the sheet gives the tank the screen again, and tapping the same pin opens it once more.
|
|
1818
|
+
- The browser console shows no errors, apart from the three "Force flush timeout" errors that applying **Full tank** logs.
|
|
1731
1819
|
|
|
1732
1820
|
## What this layout leaves out
|
|
1733
1821
|
|
|
@@ -3360,7 +3448,8 @@ export const PieceActions = ({ pieceId }: PieceActionsProps) => {
|
|
|
3360
3448
|
<Button
|
|
3361
3449
|
variant="outlined"
|
|
3362
3450
|
aria-expanded={swapOpen}
|
|
3363
|
-
|
|
3451
|
+
// The list unmounts while collapsed, so the button points at it only while it is open
|
|
3452
|
+
aria-controls={swapOpen ? 'tidepool-swap' : undefined}
|
|
3364
3453
|
onClick={() => setSwapOpen(open => !open)}
|
|
3365
3454
|
startIcon={
|
|
3366
3455
|
<svg viewBox="0 0 24 24" width={18} height={18} fill="none" stroke="currentColor" strokeWidth={1.8}>
|
|
@@ -3588,6 +3677,30 @@ export const TankInspector = () => {
|
|
|
3588
3677
|
}
|
|
3589
3678
|
```
|
|
3590
3679
|
|
|
3680
|
+
### ./src/\_inspector/pin-taps.ts
|
|
3681
|
+
|
|
3682
|
+
```ts
|
|
3683
|
+
type PinTapListener = () => void
|
|
3684
|
+
|
|
3685
|
+
const listeners = new Set<PinTapListener>()
|
|
3686
|
+
|
|
3687
|
+
// A pin is drawn inside the scene, so it cannot hand the phone page a prop. It announces each tap here, also the tap
|
|
3688
|
+
// on a piece that is already selected, which the selection alone does not show. The channel is shared by every copy of
|
|
3689
|
+
// the layout in the page; a tap there only opens a sheet, so that costs nothing.
|
|
3690
|
+
export const announcePinTap = () => {
|
|
3691
|
+
listeners.forEach(listener => listener())
|
|
3692
|
+
}
|
|
3693
|
+
|
|
3694
|
+
/** Calls `listener` on every pin tap until the returned function is called. */
|
|
3695
|
+
export const onPinTap = (listener: PinTapListener) => {
|
|
3696
|
+
listeners.add(listener)
|
|
3697
|
+
|
|
3698
|
+
return () => {
|
|
3699
|
+
listeners.delete(listener)
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
```
|
|
3703
|
+
|
|
3591
3704
|
### ./src/\_inspector/use-latest-message.ts
|
|
3592
3705
|
|
|
3593
3706
|
```ts
|
|
@@ -3861,7 +3974,7 @@ import { useEffect, useState } from 'react'
|
|
|
3861
3974
|
import type { ReactNode } from 'react'
|
|
3862
3975
|
|
|
3863
3976
|
import { phx } from '../../.phoenix/phx.js'
|
|
3864
|
-
import { Inspector, Outline } from '../_inspector/index.js'
|
|
3977
|
+
import { Inspector, Outline, onPinTap } from '../_inspector/index.js'
|
|
3865
3978
|
import type { LatestMessageState } from '../_inspector/index.js'
|
|
3866
3979
|
import { BrandMark, CARD_SX, PieceIcon, TIDEPOOL, pieceLook } from '../_theme/index.js'
|
|
3867
3980
|
import { Palette } from './Palette.js'
|
|
@@ -3945,12 +4058,16 @@ export const PhonePage = ({ messages }: PhonePageProps) => {
|
|
|
3945
4058
|
const selected = tank.$decor.find(piece => piece.id === selectedId)
|
|
3946
4059
|
const selectedLook = selected ? pieceLook(selected) : null
|
|
3947
4060
|
|
|
4061
|
+
// Adding a piece selects it, and so does a click on it in the scene; the inspector follows the new selection
|
|
3948
4062
|
useEffect(() => {
|
|
3949
4063
|
if (selectedId !== tankId) {
|
|
3950
4064
|
setSheet('inspect')
|
|
3951
4065
|
}
|
|
3952
4066
|
}, [selectedId, tankId])
|
|
3953
4067
|
|
|
4068
|
+
// A pin tap reopens the inspector for a piece that stayed selected, which the selection above does not show
|
|
4069
|
+
useEffect(() => onPinTap(() => setSheet('inspect')), [])
|
|
4070
|
+
|
|
3954
4071
|
const toggle = (next: Sheet) => setSheet(current => (current === next ? null : next))
|
|
3955
4072
|
|
|
3956
4073
|
return (
|
|
@@ -4350,12 +4467,45 @@ export const ViewSwitch = () => {
|
|
|
4350
4467
|
|
|
4351
4468
|
```ts
|
|
4352
4469
|
import { useEffect } from 'react'
|
|
4470
|
+
import type { RefObject } from 'react'
|
|
4353
4471
|
|
|
4354
4472
|
import { phx } from '../../.phoenix/phx.js'
|
|
4355
4473
|
|
|
4356
|
-
//
|
|
4357
|
-
|
|
4358
|
-
|
|
4474
|
+
// The input types that take text; in these Ctrl+Z belongs to the field
|
|
4475
|
+
const TEXT_INPUT_TYPES: ReadonlySet<string> = new Set([
|
|
4476
|
+
'text',
|
|
4477
|
+
'search',
|
|
4478
|
+
'email',
|
|
4479
|
+
'url',
|
|
4480
|
+
'tel',
|
|
4481
|
+
'password',
|
|
4482
|
+
'number',
|
|
4483
|
+
'date',
|
|
4484
|
+
'datetime-local',
|
|
4485
|
+
'month',
|
|
4486
|
+
'time',
|
|
4487
|
+
'week',
|
|
4488
|
+
])
|
|
4489
|
+
|
|
4490
|
+
const isTextEntry = (target: EventTarget | null | undefined) => {
|
|
4491
|
+
return (
|
|
4492
|
+
target instanceof HTMLTextAreaElement ||
|
|
4493
|
+
(target instanceof HTMLInputElement && TEXT_INPUT_TYPES.has(target.type)) ||
|
|
4494
|
+
(target instanceof HTMLElement && target.isContentEditable)
|
|
4495
|
+
)
|
|
4496
|
+
}
|
|
4497
|
+
|
|
4498
|
+
// The configurator sits in a closed shadow root, so to the window a key pressed in it comes from the root's host; the
|
|
4499
|
+
// root itself says which of its elements has focus
|
|
4500
|
+
const getKeyTarget = (container: HTMLElement, event: KeyboardEvent) => {
|
|
4501
|
+
const root = container.getRootNode()
|
|
4502
|
+
const [first] = event.composedPath()
|
|
4503
|
+
|
|
4504
|
+
return root instanceof ShadowRoot && first === root.host ? root.activeElement : first
|
|
4505
|
+
}
|
|
4506
|
+
|
|
4507
|
+
// Escape puts the selected piece down; Ctrl+Z or ⌘Z undoes, with Shift it redoes
|
|
4508
|
+
export const useWorkspaceKeys = (containerRef: RefObject<HTMLElement>) => {
|
|
4359
4509
|
const tankId = phx.frontend.useRootEntityId()
|
|
4360
4510
|
const selectedId = phx.frontend.useSelectedDomainEntityId()
|
|
4361
4511
|
const deselect = phx.frontend.useDeselect()
|
|
@@ -4363,10 +4513,20 @@ export const useWorkspaceKeys = () => {
|
|
|
4363
4513
|
|
|
4364
4514
|
useEffect(() => {
|
|
4365
4515
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
4366
|
-
const
|
|
4367
|
-
|
|
4516
|
+
const container = containerRef.current
|
|
4517
|
+
|
|
4518
|
+
if (!container) {
|
|
4519
|
+
return
|
|
4520
|
+
}
|
|
4521
|
+
|
|
4522
|
+
const target = getKeyTarget(container, event)
|
|
4368
4523
|
|
|
4369
|
-
|
|
4524
|
+
// Another field's keys are not the layout's; the body's are, as focus falls there when the focused button goes
|
|
4525
|
+
if (target !== document.body && !(target instanceof Node && container.contains(target))) {
|
|
4526
|
+
return
|
|
4527
|
+
}
|
|
4528
|
+
|
|
4529
|
+
if (event.defaultPrevented || isTextEntry(target)) {
|
|
4370
4530
|
return
|
|
4371
4531
|
}
|
|
4372
4532
|
|
|
@@ -4386,7 +4546,7 @@ export const useWorkspaceKeys = () => {
|
|
|
4386
4546
|
window.addEventListener('keydown', onKeyDown)
|
|
4387
4547
|
|
|
4388
4548
|
return () => window.removeEventListener('keydown', onKeyDown)
|
|
4389
|
-
}, [tankId, selectedId, deselect, undo, redo])
|
|
4549
|
+
}, [containerRef, tankId, selectedId, deselect, undo, redo])
|
|
4390
4550
|
}
|
|
4391
4551
|
```
|
|
4392
4552
|
|
|
@@ -4609,6 +4769,7 @@ export const FADE_IN = keyframes`
|
|
|
4609
4769
|
|
|
4610
4770
|
```tsx
|
|
4611
4771
|
import { Box } from '@mui/material'
|
|
4772
|
+
import { useRef } from 'react'
|
|
4612
4773
|
|
|
4613
4774
|
import { LayoutSlots } from '@salsita-ai/sdk/configurator'
|
|
4614
4775
|
|
|
@@ -4620,17 +4781,25 @@ import { useWorkspaceKeys } from '../_shared/use-workspace-keys.js'
|
|
|
4620
4781
|
import { TIDEPOOL } from '../_theme/index.js'
|
|
4621
4782
|
|
|
4622
4783
|
// A workspace over the scene. The canvas stays mounted the whole time; the desktop or the phone page lies over it.
|
|
4623
|
-
// The messages and the keyboard shortcuts live here, so they work whichever page shows and whatever is open.
|
|
4784
|
+
// The messages and the keyboard shortcuts live here, so they work whichever page shows and whatever is open. The
|
|
4785
|
+
// shortcuts act on keys pressed inside this container, or on the body, where focus falls when the focused control goes;
|
|
4786
|
+
// the container takes focus when the scene is clicked, so they work there too.
|
|
4624
4787
|
const ConfiguratorLayout = () => {
|
|
4625
4788
|
// `null` until the container has been measured; only the scene shows until then
|
|
4626
4789
|
const viewports = phx.frontend.useActiveViewports()
|
|
4627
4790
|
const messages = useLatestMessage()
|
|
4628
4791
|
const isDesktop = viewports?.includes('desktop') ?? false
|
|
4792
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
4629
4793
|
|
|
4630
|
-
useWorkspaceKeys()
|
|
4794
|
+
useWorkspaceKeys(containerRef)
|
|
4631
4795
|
|
|
4632
4796
|
return (
|
|
4633
|
-
<Box
|
|
4797
|
+
<Box
|
|
4798
|
+
component="div"
|
|
4799
|
+
ref={containerRef}
|
|
4800
|
+
tabIndex={-1}
|
|
4801
|
+
sx={{ position: 'absolute', inset: 0, overflow: 'clip', bgcolor: TIDEPOOL.foam, outline: 'none' }}
|
|
4802
|
+
>
|
|
4634
4803
|
<LayoutSlots.SharedCanvas />
|
|
4635
4804
|
{viewports !== null && (isDesktop ? <DesktopPage messages={messages} /> : <PhonePage messages={messages} />)}
|
|
4636
4805
|
</Box>
|
|
@@ -4647,11 +4816,13 @@ import { Box, ButtonBase } from '@mui/material'
|
|
|
4647
4816
|
import { useState } from 'react'
|
|
4648
4817
|
|
|
4649
4818
|
import { phx } from '../../.phoenix/phx.js'
|
|
4819
|
+
import { announcePinTap } from '../_inspector/index.js'
|
|
4650
4820
|
import { PieceIcon, TIDEPOOL, pieceLook } from '../_theme/index.js'
|
|
4651
4821
|
|
|
4652
4822
|
// Rendered by the spec's `htmlHotspot` above every piece: a pin with the piece's icon, which opens into a label with
|
|
4653
4823
|
// its name while the pointer is over the piece in the scene or over the pin, and stays open in coral while the piece
|
|
4654
|
-
// is selected. A click selects the piece
|
|
4824
|
+
// is selected. A click selects the piece and announces the tap, so the phone page opens the inspector even for a piece
|
|
4825
|
+
// that is selected already. The click goes no further, so the scene does not take it as a click on empty water.
|
|
4655
4826
|
export default phx.frontend.createCustomPhoenixEntityElement('DecorLabel', ({ entityId }) => {
|
|
4656
4827
|
const tank = phx.frontend.useRootEntityState()
|
|
4657
4828
|
const selectedId = phx.frontend.useSelectedDomainEntityId()
|
|
@@ -4677,6 +4848,7 @@ export default phx.frontend.createCustomPhoenixEntityElement('DecorLabel', ({ en
|
|
|
4677
4848
|
onClick={event => {
|
|
4678
4849
|
event.stopPropagation()
|
|
4679
4850
|
select(piece.id)
|
|
4851
|
+
announcePinTap()
|
|
4680
4852
|
}}
|
|
4681
4853
|
onPointerEnter={() => setPointed(true)}
|
|
4682
4854
|
onPointerLeave={() => setPointed(false)}
|
|
@@ -842,7 +842,10 @@ Phoenix automatically:
|
|
|
842
842
|
During `phoenix dev`, schema-compatible configurator changes can refresh the mounted application in
|
|
843
843
|
place. Changes to the domain model itself are rejected from live application and require a restart;
|
|
844
844
|
this preserves the last valid runtime and loaded configuration until migration and deployment can be
|
|
845
|
-
coordinated atomically.
|
|
845
|
+
coordinated atomically. With server persistence, restart `phoenix dev` itself: it logs an error that
|
|
846
|
+
asks for the restart, and registers the changed model with its development deployment when it starts
|
|
847
|
+
again. Then open the preview address it prints rather than a tab whose address has `?cid=`, which
|
|
848
|
+
loads a configuration saved with the earlier model.
|
|
846
849
|
|
|
847
850
|
### The phx Global Namespace
|
|
848
851
|
|
|
@@ -164,6 +164,8 @@ A project has one generated env file. Phoenix keeps it in `.phoenix/`, as `.phoe
|
|
|
164
164
|
- The deployment and environment commands (`deployment:prepare`, `deployment:prepare-dev`, `deployment:prepare-local`, `deployment:build-envs` and the others with an `--outFile` option) write `.env.generated` in the project root unless `--outFile` or `OUT_FILE` names another file. Deploy pipelines read it there. Once one of them has written it, `phoenix dev` and `phoenix build` use it.
|
|
165
165
|
- Everything that reads environment variables follows the same rule: the root `.env.generated` when it exists, otherwise `.phoenix/.env.generated`. The two files are never merged.
|
|
166
166
|
- `phoenix clean` removes `.phoenix/` and with it `.phoenix/.env.generated`; the next `phoenix dev` prepares the development deployment again. A root `.env.generated` stays.
|
|
167
|
+
- `phoenix dev` starts on the deployment the file names, and prepares a new one when the file was written for another project, environment or server, when the spec registers something on the server the deployment was not prepared with (the domain model, the published data schema, the auth hook, the roles that may create configurations, or the assistant), and on `--force`. A running `phoenix dev` does not prepare again after such a change: it logs an error that asks for a restart.
|
|
168
|
+
- Asset contents do not trigger preparation. After changing uploaded assets, restart with `phoenix dev --force` to prepare and upload them again.
|
|
167
169
|
|
|
168
170
|
This holds for a project on the application-spec pipeline. In a project on the legacy pipeline (see [Current and legacy project tooling](#current-and-legacy-project-tooling)), `phoenix dev` always reads and writes `.env.generated` in the project root.
|
|
169
171
|
|
|
@@ -305,14 +307,16 @@ Starts the full development environment: Vite dev server, development webhook tu
|
|
|
305
307
|
| `--environmentSlug` | `ENVIRONMENT_SLUG` | package.json name | Environment slug |
|
|
306
308
|
| `--withLingui` | `WITH_LINGUI` | `false` | Enable i18n extraction/compilation and watch i18n sources during dev |
|
|
307
309
|
| `--persistence` | `PERSISTENCE_MODE` | `server` | Persistence mode |
|
|
308
|
-
| `--force` / `-f` | `FORCE_REBUILD` | `false` | Force full rebuild, skip cache
|
|
310
|
+
| `--force` / `-f` | `FORCE_REBUILD` | `false` | Force full rebuild, skip cache, and prepare the development deployment again |
|
|
309
311
|
| `--serverUrl` | `SERVER_URL` | the region's server | Phoenix server URL |
|
|
310
312
|
| `--region` | `PHOENIX_REGION` | `eu` | Region of the signed-in workspace, used when no server URL is set |
|
|
311
313
|
| `--skipUpload` / `--su` | `SKIP_UPLOAD` | `false` | Skip the S3 asset upload when the development deployment is prepared at startup |
|
|
312
314
|
|
|
313
315
|
With the application-spec pipeline, `phoenix dev` creates an empty `src/` folder when the project has none, so that it rebuilds when the first source file, such as `src/webhooks/index.ts`, is added.
|
|
314
316
|
|
|
315
|
-
|
|
317
|
+
By default, with the application-spec pipeline, `phoenix dev` bootstraps missing compiled catalogs for every configured locale at startup, so a fresh project can start without a prior sync. It compiles existing `.po` translations as they are and extracts a locale only when its `.po` source is also missing. Existing compiled catalogs and `.po` sources stay unchanged. New or changed messages still need `phoenix sync` or continuous updates enabled by `--withLingui` or `WITH_LINGUI=true`.
|
|
318
|
+
|
|
319
|
+
The SDK's own Lingui CLI uses the configuration generated in `.phoenix/.linguirc`, both for `--withLingui` and when `phoenix sync` and `phoenix build` extract and compile the catalogs; see [Root Level Files](?path=/docs/phoenix-project-structure--docs#root-level-files). The dev server and `phoenix build` compile the Lingui macros (`msg`, `t`, `Trans` and the others, imported from `@lingui/core/macro`, `@lingui/react/macro` or `@lingui/macro`) with the SDK's own Lingui macro plugin and the same configuration, so the project needs neither `babel-plugin-macros` nor `@lingui/macro` for them. Babel macros of other packages are not compiled.
|
|
316
320
|
|
|
317
321
|
In `server` persistence mode the project comes from `--projectSlug`, `PROJECT_SLUG` or `project` in the spec, and the credential from `DEVELOPMENT_KEY` or the `phoenix login` sign-in; see [Starting a New Project](#starting-a-new-project). When signed in, the environment is always your own development environment and `--environmentSlug` is ignored.
|
|
318
322
|
|
|
@@ -336,7 +340,11 @@ Restart the application after fixing either failure; a subscriber or generation
|
|
|
336
340
|
observed the attempted update.
|
|
337
341
|
|
|
338
342
|
Domain-model changes are not applied live yet. Phoenix rejects them with a restart-required
|
|
339
|
-
diagnostic so the mounted application cannot observe a new model with old configuration data.
|
|
343
|
+
diagnostic so the mounted application cannot observe a new model with old configuration data. With
|
|
344
|
+
server persistence, `phoenix dev` logs an error that asks for its own restart, because the
|
|
345
|
+
development deployment keeps the schema it was prepared with until `phoenix dev` starts again and
|
|
346
|
+
prepares it. After the restart, open the preview address it prints: a tab whose address has `?cid=`
|
|
347
|
+
loads a configuration saved with the earlier model, which the new model rejects. Other
|
|
340
348
|
generated bootstrap, admin, data, environment, and host-integration surfaces should also be treated
|
|
341
349
|
as restart-required unless their feature documentation explicitly states live-update support.
|
|
342
350
|
|
|
@@ -609,7 +617,7 @@ Known limits:
|
|
|
609
617
|
- Binary assets, CSS files, locale catalog changes, dependency changes and edits requiring a full application restart are unsupported.
|
|
610
618
|
- Storybook `story-dev` has no variant previews. A project on the legacy pipeline has no variant previews; the command says so and exits.
|
|
611
619
|
|
|
612
|
-
Generated projects carry the `phoenix
|
|
620
|
+
Generated projects carry the `phoenix` agent skill, whose linked Variants guide tells coding agents when to offer options, how to write each kind and what to do with your answer.
|
|
613
621
|
|
|
614
622
|
## SDK Development
|
|
615
623
|
|
|
@@ -750,7 +758,7 @@ Revokes the token on the server and removes it from the credentials file. `--reg
|
|
|
750
758
|
|
|
751
759
|
### `phoenix deploy`
|
|
752
760
|
|
|
753
|
-
Validates the spec, packs and uploads the project, starts its build on the server and follows it until the configurator is live. Before the upload the server compares the domain model with the deployed one: a model that changed under an already deployed model version is refused with the reason, and needs a new model version and a [migration](?path=/docs/phoenix-migrations--docs). Once the build starts it prints `Follow the build in Studio: <url>`, the build's page in the Studio you signed in through; a sign-in stored by an older CLI has no Studio URL, so sign in again to get the link. After `Your configurator is live at <url>` it prints one line for coding agents, which tells them to offer the run evaluation
|
|
761
|
+
Validates the spec, packs and uploads the project, starts its build on the server and follows it until the configurator is live. Before the upload the server compares the domain model with the deployed one: a model that changed under an already deployed model version is refused with the reason, and needs a new model version and a [migration](?path=/docs/phoenix-migrations--docs). Once the build starts it prints `Follow the build in Studio: <url>`, the build's page in the Studio you signed in through; a sign-in stored by an older CLI has no Studio URL, so sign in again to get the link. After `Your configurator is live at <url>` it prints one line for coding agents, which tells them to offer the run evaluation in the Feedback guide linked from the `phoenix` skill once you have nothing more to build; you can ignore it when you run the command yourself. `--region` or `--serverUrl` selects the server, as for `login`; `--no-wait` returns as soon as the build starts.
|
|
754
762
|
|
|
755
763
|
To keep the conversation that led to a build, place a reviewed UTF-8 transcript in `conversation.log` at the project root before deploying. Deploy automatically includes it in the private source archive. Download the build's sources from Studio to read it; access is the same as for the rest of that build's sources. The build extractor skips the log, so it is not an application build input or a public asset. Missing logs produce a warning and deployment continues. Directories, links and special files at that path are rejected.
|
|
756
764
|
|
|
@@ -779,7 +787,7 @@ Pass the message as the argument for a short note, or `--file` for a longer repo
|
|
|
779
787
|
|
|
780
788
|
A message holds at most 100,000 characters, from the argument or from the file. Besides the message and the flags, the CLI sends only the SDK version, the operating system and the Node.js version; no file of the project is attached, and the Phoenix team reads a build's sources through the build's page in Studio. The project must belong to the signed-in workspace, otherwise the server refuses the feedback; without a project, the feedback is sent on its own.
|
|
781
789
|
|
|
782
|
-
Generated projects carry the `phoenix
|
|
790
|
+
Generated projects carry the `phoenix` agent skill. Its linked Feedback guide tells coding agents when to draft feedback, to show you the draft and send it only after you approve it, and how to write a report: the build id with its Studio page link, the live configurator link, what the agent was doing, what happened and what was expected, the exact error or output, the workaround and a condensed conversation log. When you say you are done, or a deploy went live and you confirm the run is finished, the agent asks you to evaluate the run, one question at a time: minutes spent, then a score from 0 to 10 for the agent's guidance, the configurator's functionality, its UX, the quality of the 3D, the UI visuals and the overall result, then your comments. It never picks a score for you and sends nothing when you decline. Secrets, credentials and personal data never go into a message; file paths, spec and code snippets and your own words may, once you approved the draft that contains them. The command needs a server with API 1.40.0 or newer; a message over 5000 characters needs API 1.43.0 or newer.
|
|
783
791
|
|
|
784
792
|
## Next Steps
|
|
785
793
|
|
|
@@ -199,21 +199,21 @@ type CustomMaterial = Phx['AdminModel']['CustomMaterial']
|
|
|
199
199
|
|
|
200
200
|
### Root Level Files
|
|
201
201
|
|
|
202
|
-
| File
|
|
203
|
-
|
|
|
204
|
-
| `package.json`
|
|
205
|
-
| `tsconfig.json`
|
|
206
|
-
| `eslint.config.js`
|
|
207
|
-
| `prettier.config.js`
|
|
208
|
-
| `.prettierignore`
|
|
209
|
-
| `pnpm-workspace.yaml`
|
|
210
|
-
| `.gitignore`
|
|
211
|
-
| `.env`
|
|
212
|
-
| `.env.example`
|
|
213
|
-
| `.env.generated`
|
|
214
|
-
| `README.md`
|
|
215
|
-
| `AGENTS.md`
|
|
216
|
-
| `.claude/skills/phoenix
|
|
202
|
+
| File | Auto-Generated? | Notes |
|
|
203
|
+
| ---------------------------------------------------- | --------------- | ------------------------------------- |
|
|
204
|
+
| `package.json` | No | Yours; see Dependencies |
|
|
205
|
+
| `tsconfig.json` | Once | Yours; see below |
|
|
206
|
+
| `eslint.config.js` | Once | Yours; see below |
|
|
207
|
+
| `prettier.config.js` | Once | Yours; see below |
|
|
208
|
+
| `.prettierignore` | No | Optional and your own; see below |
|
|
209
|
+
| `pnpm-workspace.yaml` | No | Only with pnpm; see Package manager |
|
|
210
|
+
| `.gitignore` | Partially | Only in a git repository; see Markers |
|
|
211
|
+
| `.env` | No | Git-ignored, local config |
|
|
212
|
+
| `.env.example` | No | Template for env vars |
|
|
213
|
+
| `.env.generated` | Yes | Optional, git-ignored; see below |
|
|
214
|
+
| `README.md` | No | Project documentation |
|
|
215
|
+
| `AGENTS.md` | No | Your own coding-agent instructions |
|
|
216
|
+
| `.claude/skills/phoenix/`, `.agents/skills/phoenix/` | Yes | Phoenix agent skills |
|
|
217
217
|
|
|
218
218
|
Sync does not write `README.md`, `AGENTS.md`, `.env.example`, `.env.ci` or the `README.md` files in `assets/public/` and `assets/private/`. Earlier SDK versions wrote them once; a project that has them keeps them, and they are yours to edit.
|
|
219
219
|
|
|
@@ -242,9 +242,13 @@ With the application-spec pipeline, the Lingui configuration is `.phoenix/.lingu
|
|
|
242
242
|
|
|
243
243
|
### Agent Guidance
|
|
244
244
|
|
|
245
|
-
Coding agents (Claude Code, Codex, Cursor)
|
|
245
|
+
Coding agents (Claude Code, Codex, Cursor) enter through the single `phoenix` skill under `.claude/skills/phoenix/` or `.agents/skills/phoenix/`. Its `SKILL.md` routes to the existing project, CLI, configurator workflow, code review, variants and feedback guides in `references/`. Agents read the guides for their task rather than the whole tree. The configurator workflow retains its five stages and supporting discovery, UX, asset-conversion and appearance-review references.
|
|
246
246
|
|
|
247
|
-
|
|
247
|
+
Phoenix keeps this guidance in step with the installed SDK. During the internal beta, a project created from a canary build gets the canary-update instructions in the linked project guide: once per conversation the agent checks for a newer canary and, after telling you, updates the SDK, runs `phoenix deps`, installs, then runs `phoenix sync`.
|
|
248
|
+
|
|
249
|
+
An `AGENTS.md` is yours to add; sync does not write one. Sync removes retired `phoenix-*` skill folders but leaves unrelated skills alone. If your own instructions name an earlier separate skill such as `phoenix-review`, point them at `phoenix` and the same task instead.
|
|
250
|
+
|
|
251
|
+
Projects created by earlier SDK versions had this guidance in `CLAUDE.md`, `.cursor/rules/phoenix.mdc` and `commands/review.md`. Sync removes the SDK block from the first two, keeps your own text around it, deletes a file that held nothing else, and deletes the review commands; ask `phoenix` to review instead of using `/review`.
|
|
248
252
|
|
|
249
253
|
### Markers for Partial Auto-Generation
|
|
250
254
|
|