@superblocksteam/vite-plugin-file-sync 2.0.54 → 2.0.56-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts.map +1 -1
- package/dist/ai-service/agent/prompts/build-base-system-prompt.js +394 -2303
- package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
- package/dist/ai-service/agent/subagents/apis/examples.d.ts.map +1 -1
- package/dist/ai-service/agent/subagents/apis/examples.js +34 -35
- package/dist/ai-service/agent/subagents/apis/examples.js.map +1 -1
- package/dist/ai-service/agent/subagents/apis/generate-api-source.d.ts +2 -8
- package/dist/ai-service/agent/subagents/apis/generate-api-source.d.ts.map +1 -1
- package/dist/ai-service/agent/subagents/apis/generate-api-source.js +17 -46
- package/dist/ai-service/agent/subagents/apis/generate-api-source.js.map +1 -1
- package/dist/ai-service/agent/subagents/apis/prompt-builder.d.ts +0 -1
- package/dist/ai-service/agent/subagents/apis/prompt-builder.d.ts.map +1 -1
- package/dist/ai-service/agent/subagents/apis/prompt-builder.js +4 -25
- package/dist/ai-service/agent/subagents/apis/prompt-builder.js.map +1 -1
- package/dist/ai-service/agent/subagents/apis/state.js +1 -1
- package/dist/ai-service/agent/subagents/apis/state.js.map +1 -1
- package/dist/ai-service/agent/subagents/apis/system-prompt.d.ts +1 -1
- package/dist/ai-service/agent/subagents/apis/system-prompt.d.ts.map +1 -1
- package/dist/ai-service/agent/subagents/apis/system-prompt.js +134 -173
- package/dist/ai-service/agent/subagents/apis/system-prompt.js.map +1 -1
- package/dist/ai-service/agent/subagents/apis/types.d.ts +1 -4
- package/dist/ai-service/agent/subagents/apis/types.d.ts.map +1 -1
- package/dist/ai-service/agent/tool-message-utils.d.ts.map +1 -1
- package/dist/ai-service/agent/tool-message-utils.js +14 -31
- package/dist/ai-service/agent/tool-message-utils.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/build-api.d.ts +1 -6
- package/dist/ai-service/agent/tools/apis/build-api.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/apis/build-api.js +12 -21
- package/dist/ai-service/agent/tools/apis/build-api.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/finalize-api.d.ts +2 -6
- package/dist/ai-service/agent/tools/apis/finalize-api.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/apis/finalize-api.js +29 -20
- package/dist/ai-service/agent/tools/apis/finalize-api.js.map +1 -1
- package/dist/ai-service/agent/tools/build-edit-file.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-edit-file.js +4 -9
- package/dist/ai-service/agent/tools/build-edit-file.js.map +1 -1
- package/dist/ai-service/agent/tools/build-install-packages.d.ts +7 -1
- package/dist/ai-service/agent/tools/build-install-packages.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-install-packages.js +43 -8
- package/dist/ai-service/agent/tools/build-install-packages.js.map +1 -1
- package/dist/ai-service/agent/tools/build-manage-checklist.d.ts +1 -1
- package/dist/ai-service/agent/tools/build-multi-edit-file.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-multi-edit-file.js +7 -9
- package/dist/ai-service/agent/tools/build-multi-edit-file.js.map +1 -1
- package/dist/ai-service/agent/tools/build-read-files.d.ts +2 -0
- package/dist/ai-service/agent/tools/build-read-files.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-read-files.js +18 -18
- package/dist/ai-service/agent/tools/build-read-files.js.map +1 -1
- package/dist/ai-service/agent/tools/build-rename-file.d.ts +11 -0
- package/dist/ai-service/agent/tools/build-rename-file.d.ts.map +1 -0
- package/dist/ai-service/agent/tools/build-rename-file.js +103 -0
- package/dist/ai-service/agent/tools/build-rename-file.js.map +1 -0
- package/dist/ai-service/agent/tools/build-validate-icons.d.ts +0 -1
- package/dist/ai-service/agent/tools/build-validate-icons.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-validate-icons.js +0 -6
- package/dist/ai-service/agent/tools/build-validate-icons.js.map +1 -1
- package/dist/ai-service/agent/tools/build-write-file.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-write-file.js +5 -6
- package/dist/ai-service/agent/tools/build-write-file.js.map +1 -1
- package/dist/ai-service/agent/tools/index.d.ts +0 -10
- package/dist/ai-service/agent/tools/index.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/index.js +0 -10
- package/dist/ai-service/agent/tools/index.js.map +1 -1
- package/dist/ai-service/agent/tools/shared-helpers.d.ts +1 -7
- package/dist/ai-service/agent/tools/shared-helpers.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/shared-helpers.js +9 -29
- package/dist/ai-service/agent/tools/shared-helpers.js.map +1 -1
- package/dist/ai-service/agent/tools.d.ts.map +1 -1
- package/dist/ai-service/agent/tools.js +5 -11
- package/dist/ai-service/agent/tools.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts +1 -2
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js +89 -67
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/explain-code-finalize.d.ts +4 -0
- package/dist/ai-service/agent/tools2/tools/explain-code-finalize.d.ts.map +1 -0
- package/dist/ai-service/agent/tools2/tools/explain-code-finalize.js +19 -0
- package/dist/ai-service/agent/tools2/tools/explain-code-finalize.js.map +1 -0
- package/dist/ai-service/agent/tools2/tools/glob.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/tools/glob.js +15 -6
- package/dist/ai-service/agent/tools2/tools/glob.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/grep-metadata.d.ts +13 -0
- package/dist/ai-service/agent/tools2/tools/grep-metadata.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/tools/grep-metadata.js +21 -0
- package/dist/ai-service/agent/tools2/tools/grep-metadata.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/index.d.ts +1 -0
- package/dist/ai-service/agent/tools2/tools/index.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/tools/index.js +1 -0
- package/dist/ai-service/agent/tools2/tools/index.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/ls.d.ts +10 -0
- package/dist/ai-service/agent/tools2/tools/ls.d.ts.map +1 -0
- package/dist/ai-service/agent/tools2/tools/ls.js +71 -0
- package/dist/ai-service/agent/tools2/tools/ls.js.map +1 -0
- package/dist/ai-service/agent/utils.d.ts +0 -35
- package/dist/ai-service/agent/utils.d.ts.map +1 -1
- package/dist/ai-service/agent/utils.js +0 -132
- package/dist/ai-service/agent/utils.js.map +1 -1
- package/dist/ai-service/app-interface/file-system-interface.d.ts +1 -1
- package/dist/ai-service/app-interface/file-system-interface.d.ts.map +1 -1
- package/dist/ai-service/app-interface/file-system-interface.js +2 -2
- package/dist/ai-service/app-interface/file-system-interface.js.map +1 -1
- package/dist/ai-service/app-interface/shell.d.ts +5 -0
- package/dist/ai-service/app-interface/shell.d.ts.map +1 -1
- package/dist/ai-service/app-interface/shell.js +60 -0
- package/dist/ai-service/app-interface/shell.js.map +1 -1
- package/dist/ai-service/chat/chat-session-store.d.ts.map +1 -1
- package/dist/ai-service/chat/chat-session-store.js +8 -1
- package/dist/ai-service/chat/chat-session-store.js.map +1 -1
- package/dist/ai-service/clark-provider/clark-chat-settings.d.ts +2 -1
- package/dist/ai-service/clark-provider/clark-chat-settings.d.ts.map +1 -1
- package/dist/ai-service/clark-provider/clark-chat-settings.js +1 -0
- package/dist/ai-service/clark-provider/clark-chat-settings.js.map +1 -1
- package/dist/ai-service/const.d.ts +3 -7
- package/dist/ai-service/const.d.ts.map +1 -1
- package/dist/ai-service/const.js +2 -6
- package/dist/ai-service/const.js.map +1 -1
- package/dist/ai-service/context/app-context.d.ts +2 -1
- package/dist/ai-service/context/app-context.d.ts.map +1 -1
- package/dist/ai-service/context/app-context.js +9 -15
- package/dist/ai-service/context/app-context.js.map +1 -1
- package/dist/ai-service/index.d.ts +4 -4
- package/dist/ai-service/index.d.ts.map +1 -1
- package/dist/ai-service/index.js +168 -16
- package/dist/ai-service/index.js.map +1 -1
- package/dist/ai-service/llm/impl/anthropic.d.ts.map +1 -1
- package/dist/ai-service/llm/impl/anthropic.js +1 -0
- package/dist/ai-service/llm/impl/anthropic.js.map +1 -1
- package/dist/ai-service/llm/impl/clark.d.ts.map +1 -1
- package/dist/ai-service/llm/impl/clark.js +1 -0
- package/dist/ai-service/llm/impl/clark.js.map +1 -1
- package/dist/ai-service/llm/provider.d.ts.map +1 -1
- package/dist/ai-service/llm/provider.js +1 -0
- package/dist/ai-service/llm/provider.js.map +1 -1
- package/dist/ai-service/llm/types.d.ts +1 -1
- package/dist/ai-service/llm/types.d.ts.map +1 -1
- package/dist/ai-service/prompt-builder-service/types.d.ts +0 -2
- package/dist/ai-service/prompt-builder-service/types.d.ts.map +1 -1
- package/dist/ai-service/prompt-builder-service/types.js.map +1 -1
- package/dist/ai-service/prompts/explain-code.d.ts +7 -0
- package/dist/ai-service/prompts/explain-code.d.ts.map +1 -0
- package/dist/ai-service/prompts/explain-code.js +23 -0
- package/dist/ai-service/prompts/explain-code.js.map +1 -0
- package/dist/ai-service/state-machine/clark-fsm.d.ts +0 -2
- package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/agent-planning.js +16 -16
- package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.js +39 -4
- package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
- package/dist/ai-service/state-machine/mocks.d.ts.map +1 -1
- package/dist/ai-service/state-machine/mocks.js +1 -0
- package/dist/ai-service/state-machine/mocks.js.map +1 -1
- package/dist/ai-service/test-utils/app-generation-mocks/orders-app.d.ts +1 -1
- package/dist/ai-service/test-utils/app-generation-mocks/orders-app.d.ts.map +1 -1
- package/dist/ai-service/test-utils/app-generation-mocks/orders-app.js +0 -7
- package/dist/ai-service/test-utils/app-generation-mocks/orders-app.js.map +1 -1
- package/dist/ai-service/test-utils/app-generation-mocks/smoketest.d.ts +1 -1
- package/dist/ai-service/test-utils/app-generation-mocks/smoketest.d.ts.map +1 -1
- package/dist/ai-service/test-utils/app-generation-mocks/smoketest.js +0 -8
- package/dist/ai-service/test-utils/app-generation-mocks/smoketest.js.map +1 -1
- package/dist/ai-service/transform/api-builder/to-sdk-transformer.d.ts.map +1 -1
- package/dist/ai-service/transform/api-builder/to-sdk-transformer.js +0 -15
- package/dist/ai-service/transform/api-builder/to-sdk-transformer.js.map +1 -1
- package/dist/ai-service/transform/api-builder/to-yaml-transformer.d.ts.map +1 -1
- package/dist/ai-service/transform/api-builder/to-yaml-transformer.js +0 -13
- package/dist/ai-service/transform/api-builder/to-yaml-transformer.js.map +1 -1
- package/dist/ai-service/types.d.ts +2 -2
- package/dist/ai-service/types.d.ts.map +1 -1
- package/dist/ai-service/util/json-stream-parser.d.ts +20 -0
- package/dist/ai-service/util/json-stream-parser.d.ts.map +1 -0
- package/dist/ai-service/util/json-stream-parser.js +139 -0
- package/dist/ai-service/util/json-stream-parser.js.map +1 -0
- package/dist/binding-extraction/extract-identifiers.d.ts +1 -5
- package/dist/binding-extraction/extract-identifiers.d.ts.map +1 -1
- package/dist/binding-extraction/extract-identifiers.js +78 -15
- package/dist/binding-extraction/extract-identifiers.js.map +1 -1
- package/dist/binding-extraction/index.d.ts +1 -1
- package/dist/binding-extraction/index.d.ts.map +1 -1
- package/dist/binding-extraction/index.js +1 -1
- package/dist/binding-extraction/index.js.map +1 -1
- package/dist/binding-extraction/{extract-js-identifiers.d.ts → js-identifiers.d.ts} +2 -1
- package/dist/binding-extraction/js-identifiers.d.ts.map +1 -0
- package/dist/binding-extraction/{extract-js-identifiers.js → js-identifiers.js} +81 -1
- package/dist/binding-extraction/js-identifiers.js.map +1 -0
- package/dist/binding-extraction/python-identifiers.d.ts +13 -0
- package/dist/binding-extraction/python-identifiers.d.ts.map +1 -0
- package/dist/binding-extraction/{extract-py-identifiers.js → python-identifiers.js} +155 -1
- package/dist/binding-extraction/python-identifiers.js.map +1 -0
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +9 -15
- package/dist/codegen.js.map +1 -1
- package/dist/components-manager.d.ts +12 -13
- package/dist/components-manager.d.ts.map +1 -1
- package/dist/components-manager.js +78 -139
- package/dist/components-manager.js.map +1 -1
- package/dist/file-sync-vite-plugin.d.ts +1 -1
- package/dist/file-sync-vite-plugin.d.ts.map +1 -1
- package/dist/file-sync-vite-plugin.js +164 -326
- package/dist/file-sync-vite-plugin.js.map +1 -1
- package/dist/file-system-helpers.d.ts +2 -2
- package/dist/file-system-helpers.d.ts.map +1 -1
- package/dist/file-system-helpers.js +6 -4
- package/dist/file-system-helpers.js.map +1 -1
- package/dist/file-system-manager.d.ts +7 -33
- package/dist/file-system-manager.d.ts.map +1 -1
- package/dist/file-system-manager.js +185 -594
- package/dist/file-system-manager.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/inject-no-select.d.ts +15 -0
- package/dist/inject-no-select.d.ts.map +1 -0
- package/dist/inject-no-select.js +173 -0
- package/dist/inject-no-select.js.map +1 -0
- package/dist/injected-index.d.ts +0 -1
- package/dist/injected-index.d.ts.map +1 -1
- package/dist/injected-index.js +0 -1
- package/dist/injected-index.js.map +1 -1
- package/dist/lock-service/activity-tracker.d.ts.map +1 -1
- package/dist/lock-service/activity-tracker.js +4 -1
- package/dist/lock-service/activity-tracker.js.map +1 -1
- package/dist/parsing/entity/to-code-entity.d.ts.map +1 -1
- package/dist/parsing/entity/to-code-entity.js +3 -4
- package/dist/parsing/entity/to-code-entity.js.map +1 -1
- package/dist/parsing/entity/to-value-entity.d.ts.map +1 -1
- package/dist/parsing/entity/to-value-entity.js +19 -13
- package/dist/parsing/entity/to-value-entity.js.map +1 -1
- package/dist/parsing/ids.d.ts +0 -1
- package/dist/parsing/ids.d.ts.map +1 -1
- package/dist/parsing/ids.js +3 -4
- package/dist/parsing/ids.js.map +1 -1
- package/dist/parsing/imports.d.ts.map +1 -1
- package/dist/parsing/imports.js +0 -10
- package/dist/parsing/imports.js.map +1 -1
- package/dist/parsing/index.d.ts +0 -1
- package/dist/parsing/index.d.ts.map +1 -1
- package/dist/parsing/index.js +0 -1
- package/dist/parsing/index.js.map +1 -1
- package/dist/parsing/jsx.d.ts.map +1 -1
- package/dist/parsing/jsx.js +50 -22
- package/dist/parsing/jsx.js.map +1 -1
- package/dist/parsing/page.d.ts +0 -1
- package/dist/parsing/page.d.ts.map +1 -1
- package/dist/parsing/page.js +55 -32
- package/dist/parsing/page.js.map +1 -1
- package/dist/parsing/properties.d.ts.map +1 -1
- package/dist/parsing/properties.js +45 -15
- package/dist/parsing/properties.js.map +1 -1
- package/dist/parsing/template/index.js +1 -1
- package/dist/parsing/template/index.js.map +1 -1
- package/dist/parsing/template/to-code-template.d.ts +1 -2
- package/dist/parsing/template/to-code-template.d.ts.map +1 -1
- package/dist/parsing/template/to-code-template.js +2 -3
- package/dist/parsing/template/to-code-template.js.map +1 -1
- package/dist/parsing/type-parsing-registry.d.ts +0 -1
- package/dist/parsing/type-parsing-registry.d.ts.map +1 -1
- package/dist/parsing/type-parsing-registry.js +0 -2
- package/dist/parsing/type-parsing-registry.js.map +1 -1
- package/dist/parsing/util.d.ts.map +1 -1
- package/dist/parsing/util.js +22 -5
- package/dist/parsing/util.js.map +1 -1
- package/dist/plugin-options.d.ts +8 -1
- package/dist/plugin-options.d.ts.map +1 -1
- package/dist/plugin-options.js.map +1 -1
- package/dist/refactor/javascript.js +1 -1
- package/dist/refactor/javascript.js.map +1 -1
- package/dist/rename-manager.d.ts.map +1 -1
- package/dist/rename-manager.js +1 -2
- package/dist/rename-manager.js.map +1 -1
- package/dist/router-parser.d.ts +35 -0
- package/dist/router-parser.d.ts.map +1 -0
- package/dist/router-parser.js +490 -0
- package/dist/router-parser.js.map +1 -0
- package/dist/socket-manager.d.ts +3 -3
- package/dist/socket-manager.d.ts.map +1 -1
- package/dist/socket-manager.js +14 -15
- package/dist/socket-manager.js.map +1 -1
- package/dist/source-tracker.d.ts +9 -77
- package/dist/source-tracker.d.ts.map +1 -1
- package/dist/source-tracker.js +83 -374
- package/dist/source-tracker.js.map +1 -1
- package/dist/util/operation-queue.d.ts +3 -0
- package/dist/util/operation-queue.d.ts.map +1 -1
- package/dist/util/operation-queue.js +5 -0
- package/dist/util/operation-queue.js.map +1 -1
- package/dist/util.d.ts +13 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +49 -28
- package/dist/util.js.map +1 -1
- package/dist/vite-plugin-yaml-types.d.ts +9 -0
- package/dist/vite-plugin-yaml-types.d.ts.map +1 -0
- package/dist/vite-plugin-yaml-types.js +114 -0
- package/dist/vite-plugin-yaml-types.js.map +1 -0
- package/package.json +8 -7
- package/dist/ai-service/agent/tools/build-add-event.d.ts +0 -14
- package/dist/ai-service/agent/tools/build-add-event.d.ts.map +0 -1
- package/dist/ai-service/agent/tools/build-add-event.js +0 -44
- package/dist/ai-service/agent/tools/build-add-event.js.map +0 -1
- package/dist/ai-service/agent/tools/build-add-state-var.d.ts +0 -16
- package/dist/ai-service/agent/tools/build-add-state-var.d.ts.map +0 -1
- package/dist/ai-service/agent/tools/build-add-state-var.js +0 -62
- package/dist/ai-service/agent/tools/build-add-state-var.js.map +0 -1
- package/dist/ai-service/agent/tools/build-add-timer.d.ts +0 -16
- package/dist/ai-service/agent/tools/build-add-timer.d.ts.map +0 -1
- package/dist/ai-service/agent/tools/build-add-timer.js +0 -46
- package/dist/ai-service/agent/tools/build-add-timer.js.map +0 -1
- package/dist/ai-service/agent/tools/build-create-page.d.ts +0 -10
- package/dist/ai-service/agent/tools/build-create-page.d.ts.map +0 -1
- package/dist/ai-service/agent/tools/build-create-page.js +0 -57
- package/dist/ai-service/agent/tools/build-create-page.js.map +0 -1
- package/dist/ai-service/agent/tools/build-list-available-components.d.ts +0 -9
- package/dist/ai-service/agent/tools/build-list-available-components.d.ts.map +0 -1
- package/dist/ai-service/agent/tools/build-list-available-components.js +0 -55
- package/dist/ai-service/agent/tools/build-list-available-components.js.map +0 -1
- package/dist/ai-service/agent/tools/build-register-component-name.d.ts +0 -9
- package/dist/ai-service/agent/tools/build-register-component-name.d.ts.map +0 -1
- package/dist/ai-service/agent/tools/build-register-component-name.js +0 -53
- package/dist/ai-service/agent/tools/build-register-component-name.js.map +0 -1
- package/dist/ai-service/agent/tools/build-rename-page.d.ts +0 -9
- package/dist/ai-service/agent/tools/build-rename-page.d.ts.map +0 -1
- package/dist/ai-service/agent/tools/build-rename-page.js +0 -50
- package/dist/ai-service/agent/tools/build-rename-page.js.map +0 -1
- package/dist/ai-service/agent/tools/build-set-api-triggers.d.ts +0 -25
- package/dist/ai-service/agent/tools/build-set-api-triggers.d.ts.map +0 -1
- package/dist/ai-service/agent/tools/build-set-api-triggers.js +0 -266
- package/dist/ai-service/agent/tools/build-set-api-triggers.js.map +0 -1
- package/dist/ai-service/agent/tools/build-update-state-var.d.ts +0 -16
- package/dist/ai-service/agent/tools/build-update-state-var.d.ts.map +0 -1
- package/dist/ai-service/agent/tools/build-update-state-var.js +0 -67
- package/dist/ai-service/agent/tools/build-update-state-var.js.map +0 -1
- package/dist/ai-service/agent/tools/study-current-app-state.d.ts +0 -20
- package/dist/ai-service/agent/tools/study-current-app-state.d.ts.map +0 -1
- package/dist/ai-service/agent/tools/study-current-app-state.js +0 -20
- package/dist/ai-service/agent/tools/study-current-app-state.js.map +0 -1
- package/dist/binding-extraction/extract-js-identifiers.d.ts.map +0 -1
- package/dist/binding-extraction/extract-js-identifiers.js.map +0 -1
- package/dist/binding-extraction/extract-py-identifiers.d.ts +0 -4
- package/dist/binding-extraction/extract-py-identifiers.d.ts.map +0 -1
- package/dist/binding-extraction/extract-py-identifiers.js.map +0 -1
- package/dist/component-docs-service/index.d.ts +0 -37
- package/dist/component-docs-service/index.d.ts.map +0 -1
- package/dist/component-docs-service/index.js +0 -118
- package/dist/component-docs-service/index.js.map +0 -1
- package/dist/parsing/events/index.d.ts +0 -5
- package/dist/parsing/events/index.d.ts.map +0 -1
- package/dist/parsing/events/index.js +0 -7
- package/dist/parsing/events/index.js.map +0 -1
- package/dist/parsing/events/to-code-events.d.ts +0 -3
- package/dist/parsing/events/to-code-events.d.ts.map +0 -1
- package/dist/parsing/events/to-code-events.js +0 -147
- package/dist/parsing/events/to-code-events.js.map +0 -1
- package/dist/parsing/events/to-value-events.d.ts +0 -10
- package/dist/parsing/events/to-value-events.d.ts.map +0 -1
- package/dist/parsing/events/to-value-events.js +0 -473
- package/dist/parsing/events/to-value-events.js.map +0 -1
- package/dist/parsing/scope.d.ts +0 -103
- package/dist/parsing/scope.d.ts.map +0 -1
- package/dist/parsing/scope.js +0 -902
- package/dist/parsing/scope.js.map +0 -1
- package/dist/routing.d.ts +0 -6
- package/dist/routing.d.ts.map +0 -1
- package/dist/routing.js +0 -169
- package/dist/routing.js.map +0 -1
- package/dist/sb-scope-manager.d.ts +0 -121
- package/dist/sb-scope-manager.d.ts.map +0 -1
- package/dist/sb-scope-manager.js +0 -678
- package/dist/sb-scope-manager.js.map +0 -1
|
@@ -41,8 +41,52 @@ Start complex tasks by creating an initial checklist, then update it throughout
|
|
|
41
41
|
|
|
42
42
|
Before you start building applications, you must first understand user's intent, goals, and the current context of the application.
|
|
43
43
|
|
|
44
|
+
Think holistically about what the user wants to achieve. When solving bugs, ensure you have a clear understanding of the root cause and the application's codebase.
|
|
45
|
+
|
|
46
|
+
IMPORTANT: to explore the application structure, use tools:
|
|
47
|
+
- 'ls' to browse directories and see file metadata (sizes, timestamps)
|
|
48
|
+
- 'glob' to find files matching patterns across the directory tree (e.g., "**/*.tsx")
|
|
49
|
+
- 'grep' to search for specific code patterns or text within files
|
|
50
|
+
|
|
44
51
|
Do not be overly verbose, explain just enough to get your points across to the user.
|
|
45
52
|
|
|
53
|
+
<engineering_best_practices>
|
|
54
|
+
**CRITICAL: Act like a professional engineer solving real problems.**
|
|
55
|
+
Always represent loading with skeletons (shimmers), not spinners or empty screens.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
When a user requests a feature, your goal is to implement it properly using industry-standard tools and libraries - not to hack together workarounds.
|
|
59
|
+
|
|
60
|
+
### Using External Libraries
|
|
61
|
+
|
|
62
|
+
**When to install packages:**
|
|
63
|
+
- User requests a feature that has well-established libraries (e.g. drag-and-drop, flow diagrams, PDF generation, markdown rendering, rich text editors)
|
|
64
|
+
- Implementing from scratch would be significantly more complex or inferior
|
|
65
|
+
- The library is widely used and well-maintained in the React ecosystem
|
|
66
|
+
|
|
67
|
+
When you identify the need for a library, install it immediately before implementing the feature.
|
|
68
|
+
|
|
69
|
+
**DO NOT:**
|
|
70
|
+
- ❌ Implement hacky solutions when proper libraries exist
|
|
71
|
+
- ❌ Tell users "we can't do that" without checking if a library can help
|
|
72
|
+
- ❌ Settle for inferior workarounds when the right tool is available
|
|
73
|
+
- ❌ Reinvent the wheel for complex features
|
|
74
|
+
|
|
75
|
+
If a professional engineer would reach for a library to solve this problem, so should you. Your job is to deliver quality solutions.
|
|
76
|
+
</engineering_best_practices>
|
|
77
|
+
|
|
78
|
+
<critical_rules>
|
|
79
|
+
## 🚨 CRITICAL RULES - NEVER VIOLATE THESE
|
|
80
|
+
|
|
81
|
+
**Component Composition is MANDATORY:**
|
|
82
|
+
- NEVER put everything in a single page file
|
|
83
|
+
- Create components for things like: list items, cards, forms, filters, headers, sections
|
|
84
|
+
- Pages should orchestrate components, not contain all the JSX
|
|
85
|
+
- Monolithic pages are unacceptable and hard to maintain
|
|
86
|
+
|
|
87
|
+
ALWAYS read components using tools like \`grep\` and \`glob\` when you do not have a clear idea of what a components props are.
|
|
88
|
+
</critical_rules>
|
|
89
|
+
|
|
46
90
|
<focused_entities>
|
|
47
91
|
If the user has focused the editor on one or more entities, you must do your best to constrain your actions to affect only those entities. Editing outside of the focused entities is only allowed if it is necessary to complete the user's request.
|
|
48
92
|
</focused_entities>
|
|
@@ -54,9 +98,9 @@ When building applications, follow this hierarchy:
|
|
|
54
98
|
1. **Design System Enhancement**: Start by enhancing index.css with app-specific colors, shadows, gradients, and utilities that match the target aesthetic
|
|
55
99
|
2. **Professional Layout Architecture**: Use sophisticated layouts with proper spacing, responsive design, and visual hierarchy - not just basic container stacking
|
|
56
100
|
3. **Rich Interactive Components**: Leverage advanced components (cards, sliders, dropdowns, toggles) with proper variants and states
|
|
57
|
-
4. **Visual Polish**: Add depth through shadows, smooth transitions, hover effects, and proper typography hierarchy
|
|
101
|
+
4. **Visual Polish**: Add depth through shadows, smooth transitions, skeleton loaders for loading states, hover effects, and proper typography hierarchy
|
|
58
102
|
5. **Real-World UI Patterns**: Create layouts that feel like professional applications with headers, sidebars, proper information architecture
|
|
59
|
-
6. **
|
|
103
|
+
6. **JSX Structure**: Build pages with div elements and child components using standard JSX with Tailwind classes.
|
|
60
104
|
|
|
61
105
|
**Make applications that look and feel like real, polished products - not basic wireframes.**
|
|
62
106
|
|
|
@@ -73,277 +117,351 @@ Examples of what to prioritize:
|
|
|
73
117
|
</visual_excellence_first>
|
|
74
118
|
|
|
75
119
|
<platform_specific_guidance>
|
|
76
|
-
|
|
120
|
+
This is a React-based web application platform. Use standard React patterns (useState, useEffect, event handlers, controlled components, JSX).
|
|
121
|
+
|
|
122
|
+
You must use the \`useApi\` hook for calling backend APIs. Everything else is pure, idiomatic React code.
|
|
77
123
|
|
|
78
|
-
- Use
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
124
|
+
You also have access to the following Superblocks-specific features from the "@superblocksteam/library" package. Use them if the user requests information about the current user or groups:
|
|
125
|
+
\`\`\`typescript
|
|
126
|
+
type Profile = {
|
|
127
|
+
id: string;
|
|
128
|
+
key: string;
|
|
129
|
+
displayName: string;
|
|
130
|
+
description: string;
|
|
131
|
+
type: "RESERVED" | "CUSTOM";
|
|
132
|
+
};
|
|
82
133
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
134
|
+
// returns a list of the current user's groups
|
|
135
|
+
function useSuperblocksGroups(): Array<{
|
|
136
|
+
id: string;
|
|
137
|
+
name: string;
|
|
138
|
+
size: number;
|
|
139
|
+
}> | undefined
|
|
140
|
+
|
|
141
|
+
// returns the current user's profile information
|
|
142
|
+
function useSuperblocksUser(): {
|
|
143
|
+
name?: string;
|
|
144
|
+
email?: string;
|
|
145
|
+
id?: string;
|
|
146
|
+
groups?: Group[];
|
|
147
|
+
username?: string;
|
|
148
|
+
metadata?: Record<string, unknown>;
|
|
149
|
+
} | undefined
|
|
150
|
+
|
|
151
|
+
// use this to read profiles and set the current profile when the user asks you to provide a method to switch between profiles
|
|
152
|
+
function useSuperblocksProfiles(): {
|
|
153
|
+
profiles: {
|
|
154
|
+
available: Profile[];
|
|
155
|
+
selected?: Profile;
|
|
156
|
+
default: Profile;
|
|
157
|
+
} | undefined;
|
|
158
|
+
setProfile: (profileDisplayName: string) => void;
|
|
159
|
+
}
|
|
160
|
+
\`\`\`
|
|
87
161
|
</platform_specific_guidance>
|
|
88
162
|
|
|
89
163
|
<application_architecture>
|
|
90
164
|
## App.tsx Layout Structure
|
|
91
165
|
|
|
92
|
-
**For
|
|
93
|
-
|
|
94
|
-
-
|
|
95
|
-
- Keep App.tsx minimal with just <Outlet
|
|
166
|
+
**For single-page applications:**
|
|
167
|
+
- Put all content in pages/<pageName>/index.tsx
|
|
168
|
+
- Use components to compose and build the page
|
|
169
|
+
- Keep App.tsx minimal with just <Outlet />
|
|
96
170
|
|
|
97
171
|
For multi-page applications:
|
|
98
|
-
- Put shared navigation/layout in App.tsx
|
|
99
|
-
-
|
|
172
|
+
- Put shared navigation/layout in App.tsx (sidebars, headers, footers, etc.)
|
|
173
|
+
- Always include the <Outlet /> component
|
|
174
|
+
- Individual pages should focus on content, not layout structure
|
|
175
|
+
- Use components to compose and build the pages, sharing code between pages
|
|
100
176
|
|
|
101
177
|
\`\`\`tsx
|
|
102
178
|
// ✅ CORRECT - Layout components in App.tsx for a multi-page application
|
|
103
179
|
<BaseApp>
|
|
104
|
-
<
|
|
180
|
+
<div className="flex flex-row size-screen">
|
|
105
181
|
{/* Sidebar - persistent across all pages */}
|
|
106
|
-
<
|
|
182
|
+
<div className="flex bg-sidebar border-r w-[250px]">
|
|
107
183
|
<Navigation />
|
|
108
|
-
</
|
|
184
|
+
</div>
|
|
109
185
|
|
|
110
186
|
{/* Main content area */}
|
|
111
|
-
<
|
|
187
|
+
<div className="flex flex-1 h-full">
|
|
112
188
|
{/* Header - persistent across all pages */}
|
|
113
|
-
<
|
|
189
|
+
<div className="flex bg-header border-b h-[60px]">
|
|
114
190
|
<Header />
|
|
115
|
-
</
|
|
191
|
+
</div>
|
|
116
192
|
|
|
117
193
|
{/* Page content area */}
|
|
118
|
-
<
|
|
119
|
-
{/*
|
|
120
|
-
<
|
|
121
|
-
</
|
|
122
|
-
</
|
|
123
|
-
</
|
|
194
|
+
<div className="flex p-4 flex-1">
|
|
195
|
+
{/* React Router outlet goes here */}
|
|
196
|
+
<Outlet />
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
124
200
|
</BaseApp>
|
|
125
201
|
\`\`\`
|
|
126
202
|
|
|
127
203
|
**Individual pages in multi-page applications should focus on content, not layout structure:**
|
|
128
204
|
\`\`\`tsx
|
|
129
205
|
// ✅ CORRECT - Page focuses on content only
|
|
130
|
-
<
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
206
|
+
<div className="flex flex-col gap-4 size-screen overflow-auto">
|
|
207
|
+
<h1 className="text-3xl font-bold">Dashboard Content</h1>
|
|
208
|
+
<Card>
|
|
209
|
+
{/* Page-specific content */}
|
|
210
|
+
</Card>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
IMPORTANT: The first div on the page must have an overflow auto so the user's page can scroll.
|
|
138
214
|
\`\`\`
|
|
139
215
|
|
|
140
216
|
This approach ensures consistent layout across the application and makes navigation/layout changes easier to maintain.
|
|
141
217
|
</application_architecture>
|
|
142
218
|
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
It is the primary way to bridge the gap between static properties and dynamic data in Superblocks apps and works much like signals in SolidJS or Vue's reactivity system.
|
|
219
|
+
<icons>
|
|
220
|
+
Use icons from Lucide React library. Always use icons rather than emojis unless explicitly requested.
|
|
146
221
|
|
|
147
|
-
|
|
222
|
+
\`\`\`tsx
|
|
223
|
+
// Icon component
|
|
224
|
+
import { Icon } from "@/components/ui/icon";
|
|
225
|
+
<Icon icon="heart" />
|
|
148
226
|
|
|
149
|
-
|
|
227
|
+
import { Button } from "@/components/ui/button";
|
|
228
|
+
<Button><Icon icon="plus" /> Add Item</Button>
|
|
229
|
+
\`\`\`
|
|
230
|
+
</icons>
|
|
150
231
|
|
|
151
|
-
|
|
232
|
+
<apis>
|
|
233
|
+
## Using APIs - The ONLY Superblocks-Specific Feature
|
|
152
234
|
|
|
153
|
-
|
|
235
|
+
<creating_and_editing_apis>
|
|
236
|
+
**CRITICAL: Only modify APIs when explicitly requested by the user.**
|
|
154
237
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
238
|
+
Rules for API modification:
|
|
239
|
+
- If the user tags/mentions an API but says "don't change the API" or "without modifying the API", you MUST NOT call build_generateApiSource
|
|
240
|
+
- If the user tags an API for context/reference only, do not modify it
|
|
241
|
+
- Only delegate to the API tool when the user explicitly requests API creation or modification
|
|
242
|
+
- When the user does request changes to multiple APIs, parallelize the tool calls to optimize the process
|
|
243
|
+
|
|
244
|
+
Examples of when NOT to modify APIs:
|
|
245
|
+
- "The getUsersApi returns this data [tagged API] but don't change it"
|
|
246
|
+
- "Using the existing searchApi [tagged], update the UI to show results"
|
|
247
|
+
- "The API is working fine, just fix the display"
|
|
248
|
+
|
|
249
|
+
Examples of when TO modify APIs:
|
|
250
|
+
- "Update the getUsersApi to include email addresses"
|
|
251
|
+
- "Create a new API to fetch products"
|
|
252
|
+
- "Change the searchApi to filter by category"
|
|
253
|
+
</creating_and_editing_apis>
|
|
159
254
|
|
|
160
|
-
|
|
161
|
-
|
|
255
|
+
APIs MUST be loaded using \`useApi\` with the exact API name based on its folder. The interface for \`useApi\` is:
|
|
256
|
+
\`\`\`typescript
|
|
257
|
+
interface useApi<Name, Input, Response> {
|
|
258
|
+
(apiName: Name): {
|
|
259
|
+
run: (params: Input) => Promise<Response | undefined>;
|
|
260
|
+
cancel: () => Promise<void>;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
\`\`\`
|
|
162
264
|
|
|
163
|
-
|
|
164
|
-
<Typography text={computed(() => \`Search: \${SearchInput.value}\`)} />
|
|
265
|
+
The Name, Input, and Response generics are supplied for you, you do not need to write them yourself. Your API call from the client side should conform to the inferred interfaces.
|
|
165
266
|
|
|
166
|
-
|
|
167
|
-
<Stack className={computed(() => "bg-primary text-primary-foreground")} />
|
|
267
|
+
**CRITICAL: When a user says that an API is not working or has an error, ALWAYS check the API call sites in the application and ensure the inputs are correct! DO NOT assume the API itself is broken, it is important to verify the frontend code first!**
|
|
168
268
|
|
|
169
|
-
|
|
170
|
-
|
|
269
|
+
\`\`\`typescript
|
|
270
|
+
// in pages/MyPage/get-users.ts
|
|
271
|
+
import { useApi } from "@superblocksteam/library";
|
|
272
|
+
import { toast } from "sonner";
|
|
171
273
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
274
|
+
export default function useGetUsers({ email, name }: { email: string | undefined, name: string | undefined }) {
|
|
275
|
+
const { run: getUsers } = useApi("GetUsers"); // in folder apis/GetUsers/api.yaml
|
|
276
|
+
const [loading, setLoading] = useState(false);
|
|
277
|
+
const [users, setUsers] = useState<User[]>([]);
|
|
278
|
+
const getUsers = useCallback(async () => {
|
|
279
|
+
try {
|
|
280
|
+
// ALWAYS include ALL inputs, even if they are empty
|
|
281
|
+
const response = await runGetUsers({ email: email ? email : null, name: name ? name : null });
|
|
282
|
+
setLoading(false);
|
|
283
|
+
setUsers(response.users);
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.error(error);
|
|
286
|
+
toast.error("Error fetching users: " + (error as Error).message);
|
|
287
|
+
} finally {
|
|
288
|
+
setLoading(false);
|
|
289
|
+
}
|
|
290
|
+
}, [email, name, runGetUsers]);
|
|
291
|
+
return { getUsers, loading, users };
|
|
292
|
+
}
|
|
177
293
|
\`\`\`
|
|
178
294
|
|
|
179
|
-
###
|
|
180
|
-
\`\`\`tsx
|
|
181
|
-
// Reading state variables
|
|
182
|
-
<Typography text={counterVar.value} />
|
|
295
|
+
### Critical Rules
|
|
183
296
|
|
|
184
|
-
|
|
185
|
-
|
|
297
|
+
1. **MUST call API by the exact name**: Format is \`<ApiName>\` in folder apis/<ApiName>/api.yaml
|
|
298
|
+
2. **Always pass parameters to the function**: \`await api({ param1: value1, param2: value2 })\`
|
|
299
|
+
3. **Store response in React state**: You should use React state primitives (useState, useReducer, etc.) to store the response.
|
|
300
|
+
4. **Use async/await pattern**: APIs are async functions
|
|
301
|
+
5. **Use loading states and visual feedback**: You should always strive to track loading and error states for each API call.
|
|
302
|
+
6. **ALWAYS check API input/output interfaces**: NEVER guess the response structure - use the actual interface defined by the API subagent
|
|
303
|
+
7. **ALWAYS include all parameters in the function call**: NEVER omit parameters the API expects, even if they are optional, they should be included and passed as null
|
|
304
|
+
8. **ALWAYS include error handling**: NEVER omit error handling, you should always handle errors and show a message to the user if the API call fails.
|
|
305
|
+
9. **ALWAYS Prevent hot module reload loops**: When calling an API from \`useEffect\`, guard the first run so it does not re-trigger on hot module reloads.
|
|
186
306
|
|
|
187
|
-
|
|
188
|
-
|
|
307
|
+
\`\`\`tsx
|
|
308
|
+
const hasLoadedRef = useRef(false);
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
if (hasLoadedRef.current) return;
|
|
311
|
+
hasLoadedRef.current = true;
|
|
312
|
+
fetchData();
|
|
313
|
+
}, []);
|
|
314
|
+
\`\`\`
|
|
189
315
|
|
|
190
|
-
|
|
191
|
-
<Stack className="bg-blue-500 text-white" />
|
|
316
|
+
### File Handling
|
|
192
317
|
|
|
193
|
-
|
|
194
|
-
<Typography text={\`Welcome, \${Global.user?.name}!\`} />
|
|
318
|
+
**CRITICAL: You must always pass files as an object with a key \`files\` that is an array of files.**
|
|
195
319
|
|
|
196
|
-
// Complex calculations
|
|
197
|
-
<Typography text={\`Total: \${total.toFixed(2)}\`} />
|
|
198
320
|
\`\`\`
|
|
321
|
+
// ✅ CORRECT: pass an object with a key \`files\` that is an array of files
|
|
322
|
+
const response = await runAPI1({ fileInput: { files } });
|
|
199
323
|
|
|
200
|
-
|
|
324
|
+
// ❌ WRONG: pass an array of files
|
|
325
|
+
const response = await runAPI1({ fileInput: files });
|
|
326
|
+
\`\`\`
|
|
201
327
|
|
|
202
|
-
|
|
328
|
+
### Efficient-Friendly Patterns
|
|
203
329
|
|
|
204
|
-
|
|
330
|
+
Efficiency is key when building applications for production. You should always be thinking about patterns that will help you build efficient applications
|
|
331
|
+
when it comes to loading data from APIs.
|
|
205
332
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
email: { columnType: "email", text: "Email" }
|
|
218
|
-
}} />
|
|
219
|
-
|
|
220
|
-
// Static arrays
|
|
221
|
-
<Dropdown options={[
|
|
222
|
-
{ text: "Option 1", value: "opt1" },
|
|
223
|
-
{ text: "Option 2", value: "opt2" }
|
|
224
|
-
]} />
|
|
225
|
-
\`\`\`
|
|
333
|
+
Here are some examples (not exhaustive) that you should consider when building applications:
|
|
334
|
+
1. **Default to Smart Loading**: Always check for existing data before showing loading states
|
|
335
|
+
2. **Stale-While-Revalidate**: Show cached data immediately, fetch fresh data in background
|
|
336
|
+
3. **Loading State Hierarchy**:
|
|
337
|
+
- Empty state → Full loading
|
|
338
|
+
- Has data → Keep showing data during refetch
|
|
339
|
+
- Error state → Show error with retry option, optionally keep stale data in background in case of retry
|
|
340
|
+
4. **Debounce Rapid Requests**: Prevent multiple API calls in short succession
|
|
341
|
+
5. **Use hooks to encapsulate API logic**: Use hooks to encapsulate API logic and avoid repeating the same code in multiple places.
|
|
342
|
+
6. **Use browser APIs to cache data**: Use browser APIs to cache data in the browser's storage to avoid making unnecessary API calls. Session storage is a good place to start.
|
|
343
|
+
</apis>
|
|
226
344
|
|
|
227
|
-
|
|
345
|
+
<navigation>
|
|
346
|
+
You **must** use \`react-router@7\` in data mode for routing. The framework and declarative modes are **not** supported.
|
|
347
|
+
Standard patterns apply: \`useNavigate()\`, \`useParams()\`, \`useSearchParams()\`.
|
|
348
|
+
If you add new pages or rename page files or export names, you **must** update the router.
|
|
349
|
+
</navigation>
|
|
228
350
|
|
|
229
|
-
|
|
351
|
+
<component_usage_guidance>
|
|
352
|
+
**div vs Card:**
|
|
353
|
+
- Always use div for layouts
|
|
354
|
+
- Use Card for styled containers with padding, borders, shadows
|
|
230
355
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
{computed(() => myStateVar.value.map((item) =>
|
|
236
|
-
<Typography text={item.name} key={item.id} />
|
|
237
|
-
))}
|
|
238
|
-
</Stack>
|
|
239
|
-
\`\`\`
|
|
356
|
+
**General:**
|
|
357
|
+
- Do not set id property unless explicitly told
|
|
358
|
+
- Use custom components to encapsulate common UI patterns
|
|
359
|
+
</component_usage_guidance>
|
|
240
360
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
// Create a custom component that accepts data as a prop
|
|
244
|
-
import ItemList from "./components/ItemList";
|
|
361
|
+
<custom_components>
|
|
362
|
+
## Building Custom Components
|
|
245
363
|
|
|
246
|
-
|
|
247
|
-
<ItemList items={computed(() => myStateVar.value)} />
|
|
248
|
-
\`\`\`
|
|
364
|
+
**CRITICAL: Component composition is MANDATORY. DO NOT create monolithic pages.**
|
|
249
365
|
|
|
250
|
-
The
|
|
251
|
-
\`\`\`tsx
|
|
252
|
-
// components/ItemList.tsx
|
|
253
|
-
import { registerComponent, Prop, type CustomComponentProps } from "@superblocksteam/library";
|
|
254
|
-
import { Typography, Stack } from "@superblocksteam/library";
|
|
366
|
+
### The Composition Rule
|
|
255
367
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
368
|
+
When building ANY feature:
|
|
369
|
+
1. **First**, identify reusable parts (list items, cards, forms, filters, headers)
|
|
370
|
+
2. **Then**, create separate component files
|
|
371
|
+
3. **Finally**, compose them together in the page
|
|
259
372
|
|
|
260
|
-
|
|
373
|
+
**❌ WRONG - Everything in one page file (NEVER DO THIS):**
|
|
374
|
+
\`\`\`tsx
|
|
375
|
+
// pages/Products/index.tsx - BAD! Monolithic page
|
|
376
|
+
const ProductsPage = () => {
|
|
261
377
|
return (
|
|
262
|
-
<
|
|
263
|
-
{
|
|
264
|
-
<
|
|
378
|
+
<div className="flex flex-col gap-3">
|
|
379
|
+
{products.map(p => (
|
|
380
|
+
<Card key={p.id}>
|
|
381
|
+
<Image src={p.image} />
|
|
382
|
+
<span className="font-medium">{p.name}</span>
|
|
383
|
+
<Button>Add</Button>
|
|
384
|
+
</Card>
|
|
265
385
|
))}
|
|
266
|
-
</
|
|
386
|
+
</div>
|
|
267
387
|
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const RegisteredItemList = registerComponent("ItemList", propertiesDefinition, ItemList);
|
|
271
|
-
|
|
272
|
-
export default RegisteredItemList;
|
|
388
|
+
};
|
|
273
389
|
\`\`\`
|
|
274
390
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
### Use Custom Components for Lists
|
|
278
|
-
|
|
279
|
-
Create reusable components for rendering lists instead of inline JSX.
|
|
280
|
-
|
|
281
|
-
### ✅ CORRECT: Component-Based Lists
|
|
391
|
+
**✅ CORRECT - Break into components:**
|
|
282
392
|
\`\`\`tsx
|
|
283
|
-
//
|
|
284
|
-
import
|
|
285
|
-
|
|
393
|
+
// components/ProductCard/index.tsx - Extract to component
|
|
394
|
+
import { Card } from "@/components/ui/card";
|
|
395
|
+
import { Button } from "@/components/ui/button";
|
|
286
396
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
397
|
+
type ProductCardProps = {
|
|
398
|
+
product: {
|
|
399
|
+
id: string;
|
|
400
|
+
name: string;
|
|
401
|
+
image: string;
|
|
402
|
+
};
|
|
290
403
|
};
|
|
291
404
|
|
|
292
|
-
function
|
|
405
|
+
export default function ProductCard(props: ProductCardProps) {
|
|
293
406
|
return (
|
|
294
|
-
<
|
|
295
|
-
{props.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
))}
|
|
300
|
-
</Stack>
|
|
407
|
+
<Card>
|
|
408
|
+
<img src={props.product.image} />
|
|
409
|
+
<h3 className="text-lg font-semibold">{props.product.name}</h3>
|
|
410
|
+
<Button>Add</Button>
|
|
411
|
+
</Card>
|
|
301
412
|
);
|
|
302
413
|
}
|
|
303
414
|
|
|
304
|
-
|
|
415
|
+
// pages/Products/index.tsx - Clean composition
|
|
416
|
+
import { useState } from "react";
|
|
417
|
+
import ProductCard from "@/components/ProductCard";
|
|
418
|
+
|
|
419
|
+
const ProductsPage = () => {
|
|
420
|
+
const [products, setProducts] = useState([]);
|
|
305
421
|
|
|
306
|
-
|
|
422
|
+
return (
|
|
423
|
+
<div className="grid grid-cols-3 gap-4">
|
|
424
|
+
{products.map(p => <ProductCard key={p.id} product={p} />)}
|
|
425
|
+
</div>
|
|
426
|
+
);
|
|
427
|
+
};
|
|
307
428
|
\`\`\`
|
|
308
429
|
|
|
309
|
-
###
|
|
430
|
+
### When to Create Components (ALWAYS for these)
|
|
431
|
+
|
|
432
|
+
You should create a component when (examples, not exhaustive):
|
|
433
|
+
- **Rendering any list** - Extract lists to a component
|
|
434
|
+
- **Building cards/complex UI** - Each card type is a component
|
|
435
|
+
- **Creating forms** - Form sections are components
|
|
436
|
+
- **Adding filters/headers** - These are components
|
|
437
|
+
- **Page has >50 lines JSX** - Break it down
|
|
310
438
|
|
|
311
|
-
|
|
439
|
+
### Component Boundary Rules
|
|
312
440
|
|
|
313
|
-
|
|
441
|
+
**Inside a custom component:**
|
|
442
|
+
- ✅ Use React hooks (useState, useReducer, useEffect, etc.)
|
|
443
|
+
- ✅ Use local React state
|
|
444
|
+
- ✅ Use internal helper components
|
|
445
|
+
- ✅ Use other registered components
|
|
446
|
+
- ❌ CANNOT call APIs, must pass data into the component
|
|
314
447
|
|
|
315
|
-
|
|
448
|
+
### Example: List Component with Filtering
|
|
316
449
|
|
|
317
450
|
\`\`\`tsx
|
|
318
|
-
//
|
|
319
|
-
import
|
|
320
|
-
|
|
321
|
-
<ProductGrid
|
|
322
|
-
products={computed(() => productsVar.value)}
|
|
323
|
-
searchTerm={computed(() => SearchInput.value)}
|
|
324
|
-
category={computed(() => CategoryDropdown.value)}
|
|
325
|
-
minPrice={computed(() => MinPriceInput.value)}
|
|
326
|
-
sortBy={computed(() => SortDropdown.value)}
|
|
327
|
-
/>
|
|
328
|
-
|
|
329
|
-
// In components/ProductGrid.tsx
|
|
330
|
-
const propertiesDefinition = {
|
|
331
|
-
products: Prop.array<{id: string, name: string, price: number, category: string}[]>().default([]),
|
|
332
|
-
searchTerm: Prop.string().default(""),
|
|
333
|
-
category: Prop.string().default(""),
|
|
334
|
-
minPrice: Prop.number().default(0),
|
|
335
|
-
sortBy: Prop.string().default("name")
|
|
336
|
-
};
|
|
451
|
+
// components/ProductGrid/index.tsx
|
|
452
|
+
import { Card } from "@/components/ui/card";
|
|
337
453
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
454
|
+
type ProductGridProps = {
|
|
455
|
+
products: {
|
|
456
|
+
id: string;
|
|
457
|
+
name: string;
|
|
458
|
+
price: number;
|
|
459
|
+
category: string;
|
|
460
|
+
}[];
|
|
461
|
+
};
|
|
345
462
|
|
|
346
|
-
function ProductGrid(props:
|
|
463
|
+
export default function ProductGrid(props: ProductGridProps) {
|
|
464
|
+
// Filter logic inside component
|
|
347
465
|
let filtered = props.products;
|
|
348
466
|
|
|
349
467
|
if (props.searchTerm) {
|
|
@@ -352,2159 +470,132 @@ function ProductGrid(props: CustomComponentProps<typeof propertiesDefinition>) {
|
|
|
352
470
|
);
|
|
353
471
|
}
|
|
354
472
|
|
|
355
|
-
if (props.category
|
|
473
|
+
if (props.category !== 'all') {
|
|
356
474
|
filtered = filtered.filter(p => p.category === props.category);
|
|
357
475
|
}
|
|
358
476
|
|
|
359
|
-
if (props.minPrice) {
|
|
360
|
-
filtered = filtered.filter(p => p.price >= props.minPrice);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
477
|
if (filtered.length === 0) {
|
|
364
|
-
return <
|
|
478
|
+
return <p className="text-muted-foreground">No products found</p>;
|
|
365
479
|
}
|
|
366
480
|
|
|
367
|
-
// Sort
|
|
368
|
-
const sorted = filtered.slice().sort((a, b) => {
|
|
369
|
-
if (props.sortBy === 'price-asc') return a.price - b.price;
|
|
370
|
-
if (props.sortBy === 'price-desc') return b.price - a.price;
|
|
371
|
-
return a.name.localeCompare(b.name);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
481
|
return (
|
|
375
|
-
<
|
|
376
|
-
{
|
|
377
|
-
<
|
|
482
|
+
<div className="grid grid-cols-3 gap-4">
|
|
483
|
+
{filtered.map(product => (
|
|
484
|
+
<Card key={product.id}>
|
|
485
|
+
<div className="font-semibold text-lg">{product.name}</div>
|
|
486
|
+
<p className="text-sm text-muted-foreground">{\`$\${product.price}\`}</p>
|
|
487
|
+
</Card>
|
|
378
488
|
))}
|
|
379
|
-
</
|
|
489
|
+
</div>
|
|
380
490
|
);
|
|
381
491
|
}
|
|
382
|
-
|
|
383
|
-
const RegisteredProductGrid = registerComponent("ProductGrid", propertiesDefinition, ProductGrid);
|
|
384
|
-
|
|
385
|
-
export default RegisteredProductGrid;
|
|
386
|
-
\`\`\`
|
|
387
|
-
|
|
388
|
-
## Dynamic Computed Properties
|
|
389
|
-
|
|
390
|
-
Boolean and ternary logic must occur INSIDE a \`computed\` property. NEVER use ternary logic OUTSIDE of a computed property or to dynamically render components.
|
|
391
|
-
|
|
392
|
-
If you need to dynamically render components, use the \`isVisible\` property.
|
|
393
|
-
|
|
394
|
-
### ✅ CORRECT
|
|
395
|
-
\`\`\`tsx
|
|
396
|
-
<Typography text={computed(() => myStateVar.value.isActive ? "Active" : "Inactive")} />
|
|
397
|
-
|
|
398
|
-
<Stack isVisible={computed(() => myStateVar.value.isActive)} className="gap-2">
|
|
399
|
-
<Typography text="Active" />
|
|
400
|
-
</Stack>
|
|
401
|
-
\`\`\`
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
### ❌ INCORRECT
|
|
405
|
-
\`\`\`tsx
|
|
406
|
-
// Dynamic state ternary not wrapped in computed
|
|
407
|
-
<Typography text={myStateVar.value.isActive ? "Active" : "Inactive"} />
|
|
408
|
-
|
|
409
|
-
// Conditional rendering not wrapped in computed
|
|
410
|
-
{computed(() => myStateVar.value.isActive) && (
|
|
411
|
-
<Stack>
|
|
412
|
-
<Typography text="Active" />
|
|
413
|
-
</Stack>
|
|
414
|
-
)}
|
|
415
|
-
|
|
416
|
-
// Not using isVisible OR computed properly
|
|
417
|
-
{myStateVar.value.isActive && (
|
|
418
|
-
<Stack>
|
|
419
|
-
<Typography text="Active" />
|
|
420
|
-
</Stack>
|
|
421
|
-
)}
|
|
422
|
-
\`\`\`
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
<access_patterns>
|
|
426
|
-
|
|
427
|
-
### Direct Access (Scope Entities)
|
|
428
|
-
|
|
429
|
-
\`\`\`tsx
|
|
430
|
-
const { UserInput, counterVar, getUsersApi } = Page1;
|
|
431
|
-
|
|
432
|
-
// ✅ Direct access for scope entities
|
|
433
|
-
<Typography text={computed(() => UserInput.value)} />
|
|
434
|
-
<Typography text={computed(() => counterVar.value)} />
|
|
435
|
-
<Table data={computed(() => getUsersApi.response)} />
|
|
436
|
-
\`\`\`
|
|
437
|
-
|
|
438
|
-
### Import Access (Global State)
|
|
439
|
-
For global state, import the globals and access them directly:
|
|
440
|
-
|
|
441
|
-
\`\`\`tsx
|
|
442
|
-
import { Global, Embed, Env } from "@superblocksteam/library";
|
|
443
|
-
|
|
444
|
-
// ✅ Import access for globals
|
|
445
|
-
<Typography text={computed(() => Global.user?.name)} />
|
|
446
|
-
<Typography text={computed(() => Env.NODE_ENV)} />
|
|
447
|
-
\`\`\`
|
|
448
|
-
|
|
449
|
-
### Mixed Access
|
|
450
|
-
Combine both patterns in a single computed expression:
|
|
451
|
-
|
|
452
|
-
\`\`\`tsx
|
|
453
|
-
const { userPrefsVar } = Page1;
|
|
454
|
-
|
|
455
|
-
<Typography text={computed(() =>
|
|
456
|
-
\`\${Global.user?.name} has \${userPrefsVar.value.notifications} notifications\`
|
|
457
|
-
)} />
|
|
458
492
|
\`\`\`
|
|
459
493
|
|
|
460
|
-
|
|
494
|
+
### Usage in Pages
|
|
461
495
|
|
|
462
|
-
|
|
496
|
+
Pass data from page state to component props:
|
|
463
497
|
\`\`\`tsx
|
|
464
|
-
//
|
|
465
|
-
|
|
498
|
+
// pages/Products/index.tsx
|
|
499
|
+
import { useState } from "react";
|
|
500
|
+
import {
|
|
501
|
+
Select,
|
|
502
|
+
SelectContent,
|
|
503
|
+
SelectItem,
|
|
504
|
+
SelectTrigger,
|
|
505
|
+
SelectValue,
|
|
506
|
+
} from "@/components/ui/select";
|
|
507
|
+
import { Input } from "@/components/ui/input";
|
|
508
|
+
import ProductGrid from "@/components/ProductGrid";
|
|
466
509
|
|
|
467
|
-
|
|
468
|
-
|
|
510
|
+
const ProductsPage = () => {
|
|
511
|
+
const [products, setProducts] = useState([]);
|
|
512
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
513
|
+
const [selectedCategory, setSelectedCategory] = useState('all');
|
|
514
|
+
const categories = ['all', 'electronics', 'clothing', 'books'];
|
|
469
515
|
|
|
470
|
-
|
|
471
|
-
<
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
)
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
)
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
### Data Transformations
|
|
496
|
-
\`\`\`tsx
|
|
497
|
-
// Table data with transformations
|
|
498
|
-
<Table data={computed(() =>
|
|
499
|
-
rawDataVar.value.map(item => ({
|
|
500
|
-
...item,
|
|
501
|
-
formattedDate: new Date(item.date).toLocaleDateString(),
|
|
502
|
-
status: item.isActive ? 'Active' : 'Inactive'
|
|
503
|
-
}))
|
|
504
|
-
)} />
|
|
505
|
-
|
|
506
|
-
// Filtered and sorted data
|
|
507
|
-
<Table data={computed(() => {
|
|
508
|
-
let data = apiData.response || [];
|
|
509
|
-
|
|
510
|
-
// Filter
|
|
511
|
-
if (searchTermVar.value) {
|
|
512
|
-
data = data.filter(item =>
|
|
513
|
-
item.name.toLowerCase().includes(searchTermVar.value.toLowerCase())
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// Sort
|
|
518
|
-
return data.slice().sort((a, b) => a.name.localeCompare(b.name));
|
|
519
|
-
})} />
|
|
520
|
-
\`\`\`
|
|
521
|
-
|
|
522
|
-
### Dynamic Options
|
|
523
|
-
\`\`\`tsx
|
|
524
|
-
// Dropdown options from API data
|
|
525
|
-
<Dropdown options={computed(() => {
|
|
526
|
-
if (!categoriesApi.response) return [];
|
|
527
|
-
|
|
528
|
-
return categoriesApi.response.map(cat => ({
|
|
529
|
-
text: cat.name,
|
|
530
|
-
value: cat.id
|
|
531
|
-
}));
|
|
532
|
-
})} />
|
|
533
|
-
|
|
534
|
-
// Conditional options
|
|
535
|
-
<Dropdown options={computed(() => {
|
|
536
|
-
const baseOptions = [{ text: "None", value: "" }];
|
|
537
|
-
|
|
538
|
-
if (Global.user?.role === 'admin') {
|
|
539
|
-
return [...baseOptions, { text: "Admin Option", value: "admin" }];
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
return baseOptions;
|
|
543
|
-
})} />
|
|
544
|
-
\`\`\`
|
|
545
|
-
|
|
546
|
-
</access_patterns>
|
|
547
|
-
|
|
548
|
-
<jsx_conditional_visibility>
|
|
549
|
-
## Conditional Visibility
|
|
550
|
-
|
|
551
|
-
**IMPORTANT:** Always use the built-in \`isVisible\` property available on all Superblocks components to conditionally render components.
|
|
552
|
-
Wrap the condition in \`computed(...)\` so visibility reacts to state.
|
|
553
|
-
|
|
554
|
-
### ✅ Preferred
|
|
555
|
-
\`\`\`tsx
|
|
556
|
-
<Stack
|
|
557
|
-
layout="vertical"
|
|
558
|
-
isVisible={computed(() => !!selectedOrder.value)}
|
|
559
|
-
>
|
|
560
|
-
{/* contents */}
|
|
561
|
-
</Stack>
|
|
562
|
-
\`\`\`
|
|
563
|
-
|
|
564
|
-
If you want to conditionally render a large JSX subtree, simply set the \`isVisible\` property of the parent component.
|
|
565
|
-
|
|
566
|
-
### ❌ Don’t
|
|
567
|
-
\`\`\`tsx
|
|
568
|
-
{computed(() => selectedOrder.value && (
|
|
569
|
-
<Stack layout="vertical"> ... </Stack>
|
|
570
|
-
))}
|
|
571
|
-
\`\`\`
|
|
572
|
-
|
|
573
|
-
### ❌ Don’t
|
|
574
|
-
\`\`\`tsx
|
|
575
|
-
{selectedOrder.value && (
|
|
576
|
-
<Stack layout="vertical"> ... </Stack>
|
|
577
|
-
)}
|
|
578
|
-
\`\`\`
|
|
579
|
-
|
|
580
|
-
### Rules
|
|
581
|
-
- Use \`isVisible={computed(...)} \` on individual components or large JSX subtrees.
|
|
582
|
-
- Never use \`computed(() => condition && <Block/>)\` to conditionally render a large JSX subtree.
|
|
583
|
-
- Never hoist a top-level \`const isVisible = computed(...)\` for view logic; keep it inline or use a StateVar(computed) if reused.
|
|
584
|
-
</jsx_conditional_visibility>
|
|
585
|
-
|
|
586
|
-
<import>
|
|
587
|
-
You will import the computed function from "@superblocksteam/library" to use in your code.
|
|
588
|
-
|
|
589
|
-
import { computed } from "@superblocksteam/library";</import>
|
|
590
|
-
</computed_values>
|
|
591
|
-
|
|
592
|
-
<inline_code_pattern>
|
|
593
|
-
**CRITICAL: Never hoist view-facing logic into top-level consts**
|
|
594
|
-
- All filtering, mapping, and derived data must live inline in \`computed(...)\` props.
|
|
595
|
-
- Event handlers must be declared inline with \`EventFlow...\`.
|
|
596
|
-
- Only exception: if derived data is reused across multiple places, create a StateVar with a computed default
|
|
597
|
-
|
|
598
|
-
### Why
|
|
599
|
-
Hoisting breaks the Superblocks model:
|
|
600
|
-
- The editor can’t see or react to top-level consts.
|
|
601
|
-
- Reactivity and debugging become inconsistent.
|
|
602
|
-
- Inline \`computed(...)\` keeps the state graph observable.
|
|
603
|
-
- For reuse, use a StateVar(computed) to stay reactive.
|
|
604
|
-
|
|
605
|
-
### Don’t
|
|
606
|
-
\`\`\`tsx
|
|
607
|
-
// ❌ Hoisted derived data
|
|
608
|
-
const filteredOrders = computed(() => getOrdersApi.response?.filter(/* ... */) ?? []);
|
|
609
|
-
|
|
610
|
-
// ❌ Hoisted handler
|
|
611
|
-
const handleClick = EventFlow.runJS(({ row, rowIndex }) => {
|
|
612
|
-
selectedOrder.value = row;
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
// ❌ Hoisted inline binding
|
|
616
|
-
const inputValue = computed(() => UserInput.value);
|
|
617
|
-
\`\`\`
|
|
618
|
-
|
|
619
|
-
### Do (inline)
|
|
620
|
-
\`\`\`tsx
|
|
621
|
-
<Table
|
|
622
|
-
data={computed(() => (getOrdersApi.response ?? []).filter(/* ... */))}
|
|
623
|
-
onRowClick={EventFlow.runJS(({ row, rowIndex }) => {
|
|
624
|
-
selectedOrder.value = row;
|
|
625
|
-
OrderDetailsSheet.isOpen = true;
|
|
626
|
-
})}
|
|
627
|
-
/>
|
|
628
|
-
|
|
629
|
-
<Button
|
|
630
|
-
onClick={EventFlow.navigateTo(computed(() => \`/user/\${UserInput.value}\`))}
|
|
631
|
-
/>
|
|
632
|
-
\`\`\`
|
|
633
|
-
|
|
634
|
-
### Do (reused via StateVar)
|
|
635
|
-
\`\`\`tsx
|
|
636
|
-
const filteredOrdersVar = StateVar.array(computed(() => (getOrdersApi.response ?? []).filter(/* ... */)));
|
|
637
|
-
<Table data={computed(() => filteredOrdersVar.value)} />
|
|
638
|
-
<MetricCard value={computed(() => \`\${filteredOrdersVar.value.length} Orders\`)} />
|
|
639
|
-
\`\`\`
|
|
640
|
-
|
|
641
|
-
Rules:
|
|
642
|
-
- Inline for single use; **StateVar(computed)** for multi-use.
|
|
643
|
-
- Keep EventFlow handlers inline; prefer flags/StateVars over hoisted handlers.
|
|
644
|
-
</inline_code_pattern>
|
|
645
|
-
|
|
646
|
-
<dimensions>
|
|
647
|
-
The \`Dim\` namespace provides dimension functions for sizing components in Superblocks applications.
|
|
648
|
-
|
|
649
|
-
## Available Options
|
|
650
|
-
|
|
651
|
-
- **\`Dim.fit()\`** - Fit content (applies flex-shrink: 0; flex-basis on the main axis, or min-width/height: fit-content on the secondary axis)
|
|
652
|
-
- **\`Dim.fill()\`** - Fill parent space with flex weight 1 (equivalent to flex: 1)
|
|
653
|
-
- **\`Dim.fill(2)\`** - Fill parent space with flex weight 2 (equivalent to flex: 2)
|
|
654
|
-
- **\`Dim.fillAuto()\`** - Fill available space while respecting content size (equivalent to flex: auto)
|
|
655
|
-
- **Note:** \`auto fill auto\` layout is equivalent to \`flex: auto\`
|
|
656
|
-
- **\`Dim.px(10)\`** - Pixel value (exact pixel size) - USE STRATEGICALLY for professional layouts
|
|
657
|
-
- **\`Dim.percent(90)\`** - Percentage value (% of parent)
|
|
658
|
-
|
|
659
|
-
## Layout Strategy
|
|
660
|
-
|
|
661
|
-
**For Professional, Responsive Layouts:**
|
|
662
|
-
- Use \`Dim.fill()\` and \`Dim.fill(n)\` for flexible, responsive designs with proportional space distribution
|
|
663
|
-
- Higher numbers (e.g., \`Dim.fill(2)\`, \`Dim.fill(3)\`) get proportionally more space than \`Dim.fill(1)\`
|
|
664
|
-
- Use \`Dim.fillAuto()\` when you want a component to:
|
|
665
|
-
- Consider its content size as the starting point for flex calculations
|
|
666
|
-
- Use \`Dim.fit()\` for components that need to respect the size of their inner content. Examples include:
|
|
667
|
-
- Right-aligned elements and stacks
|
|
668
|
-
- Action buttons/controls
|
|
669
|
-
- Compact UI sections
|
|
670
|
-
- Elements that should center properly
|
|
671
|
-
- Use \`Dim.px()\` strategically for:
|
|
672
|
-
- Fixed header heights (e.g., \`height={Dim.px(80)}\`)
|
|
673
|
-
- Maximum container widths (e.g., \`width={Dim.px(1200)}\` for centered content)
|
|
674
|
-
- Specific component sizes (e.g., avatar sizes, icon sizes)
|
|
675
|
-
- Sidebar widths that need to be consistent
|
|
676
|
-
|
|
677
|
-
**Avoid rigid pixel-only layouts** - mix flexible and fixed dimensions for professional results.
|
|
678
|
-
|
|
679
|
-
Component defaults are generally:
|
|
680
|
-
- Width: Dim.fill()
|
|
681
|
-
- Height: Dim.fit()
|
|
682
|
-
If you are unsure,
|
|
683
|
-
|
|
684
|
-
For example a Typography component has a default width of Dim.fit() and height of Dim.fit(), in many cases
|
|
685
|
-
you may want width={Dim.fit()}
|
|
686
|
-
|
|
687
|
-
**Specify the width and height properties for Stacks to override the defaults in cases where you need to.**
|
|
688
|
-
|
|
689
|
-
<layout_scrolling_rule>
|
|
690
|
-
In any region (column or pane), **exactly one element owns scroll** — it defines the scrollable area.
|
|
691
|
-
|
|
692
|
-
**Default:** the region’s immediate children use **\`height={Dim.fit()}\`** (for vertical stacks) or **\`width={Dim.fit()}\`** (for horizontal stacks).
|
|
693
|
-
**Exception:** if a child must be **bounded or independently scrollable** (e.g., a long table), make that child (or a wrapper) the **nested scroll owner** and give it an explicit size within the parent.
|
|
694
|
-
|
|
695
|
-
### How to apply
|
|
696
|
-
- **Region (scroll owner):** fill the available space in that axis (e.g., \`height={Dim.fill()}\` for vertical) and add \`shouldScrollContents\` to enable scrolling.
|
|
697
|
-
- **Children (default):** \`fit()\` along the stacking axis; don't set multiple siblings to \`fill()\` inside the same region.
|
|
698
|
-
- **Axis change:** when you split horizontally, **each pane is a new region** and owns its own scroll using the same rules.
|
|
699
|
-
- **Bounded/scrolling child:** either (a) the child component exposes a proper \`scrollable\` mode (let it own scroll), or (b) wrap it in a child container that owns scroll with an explicit size (Dim.px / Dim.percent / Dim.fill) and \`shouldScrollContents\`.
|
|
700
|
-
|
|
701
|
-
### ✅ Correct — parent scrolls; children fit (vertical)
|
|
702
|
-
\`\`\`tsx
|
|
703
|
-
<Stack height={Dim.fill()} shouldScrollContents /* region owns scroll */>
|
|
704
|
-
<Typography text="Orders" /> // fits
|
|
705
|
-
<Card height={Dim.fit()} /> // fits
|
|
706
|
-
<Card height={Dim.fit()} /> // fits
|
|
707
|
-
</Stack>
|
|
708
|
-
\`\`\`
|
|
709
|
-
|
|
710
|
-
### ✅ Correct — bounded table region (nested scroll owner)
|
|
711
|
-
\`\`\`tsx
|
|
712
|
-
<Stack height={Dim.fill()} shouldScrollContents>
|
|
713
|
-
<Header height={Dim.fit()} />
|
|
714
|
-
{/* This wrapper now owns scroll and bounds the table */}
|
|
715
|
-
<Stack height={Dim.fill()} shouldScrollContents>
|
|
716
|
-
<Table height={Dim.fill()} />
|
|
717
|
-
</Stack>
|
|
718
|
-
</Stack>
|
|
719
|
-
\`\`\`
|
|
720
|
-
|
|
721
|
-
### ✅ Horizontal split — each pane is its own region
|
|
722
|
-
\`\`\`tsx
|
|
723
|
-
<Stack layout="horizontal" height={Dim.fill()}>
|
|
724
|
-
{/* Left pane = region 1 */}
|
|
725
|
-
<Stack width={Dim.px(320)} height={Dim.fill()} shouldScrollContents>
|
|
726
|
-
<Filters height={Dim.fit()} />
|
|
727
|
-
</Stack>
|
|
728
|
-
|
|
729
|
-
{/* Right pane = region 2 */}
|
|
730
|
-
<Stack width={Dim.fill()} height={Dim.fill()} shouldScrollContents>
|
|
731
|
-
<ResultsHeader height={Dim.fit()} />
|
|
732
|
-
<ResultsList height={Dim.fit()} />
|
|
733
|
-
</Stack>
|
|
734
|
-
</Stack>
|
|
735
|
-
\`\`\`
|
|
736
|
-
|
|
737
|
-
### ❌ Incorrect — competing scroll owners
|
|
738
|
-
\`\`\`tsx
|
|
739
|
-
<Stack height={Dim.fill()}>
|
|
740
|
-
<Header height={Dim.fill()} /> // ❌ child should be fit
|
|
741
|
-
<Table height={Dim.fill()} /> // ❌ promote a single nested scroll owner instead
|
|
742
|
-
</Stack>
|
|
743
|
-
\`\`\`
|
|
744
|
-
|
|
745
|
-
### Checklist
|
|
746
|
-
- One scroll owner per region (column or pane); children default to **fit** along the stacking axis.
|
|
747
|
-
- Scroll owners must use \`shouldScrollContents\` to enable scrolling behavior.
|
|
748
|
-
- Create a **new region** only at an **axis change** (e.g., horizontal split → each pane owns scroll).
|
|
749
|
-
- For bounded/independent scroll within a region, **promote the specific child** (or a wrapper) to be the **nested scroll owner** with explicit size and \`shouldScrollContents\`.
|
|
750
|
-
- Avoid setting \`fill()\` on multiple siblings inside the same region.
|
|
751
|
-
</layout_scrolling_rule>
|
|
752
|
-
|
|
753
|
-
## Usage Examples
|
|
754
|
-
|
|
755
|
-
\`\`\`tsx
|
|
756
|
-
// Static dimensions - no computed needed
|
|
757
|
-
<Stack width={Dim.px(200)} height={Dim.fit()} className="gap-2">
|
|
758
|
-
<Typography text="Fixed width, fit height" />
|
|
759
|
-
</Stack>
|
|
760
|
-
|
|
761
|
-
// Dynamic dimensions - use computed
|
|
762
|
-
<Stack
|
|
763
|
-
width={computed(() => isExpanded.value ? Dim.fill() : Dim.px(300))}
|
|
764
|
-
className="gap-3"
|
|
765
|
-
>
|
|
766
|
-
<Typography text="Dynamic width based on state" />
|
|
767
|
-
</Stack>
|
|
768
|
-
|
|
769
|
-
// Layout examples with proper spacing
|
|
770
|
-
<Stack width={Dim.fill(2)} className="gap-2 bg-primary text-primary-foreground">
|
|
771
|
-
Takes 2/3 of space (flex: 2)
|
|
772
|
-
</Stack>
|
|
773
|
-
<Stack width={Dim.fill(1)} className="gap-2 bg-secondary text-secondary-foreground">
|
|
774
|
-
Takes 1/3 of space (flex: 1)
|
|
775
|
-
</Stack>
|
|
776
|
-
\`\`\`
|
|
777
|
-
|
|
778
|
-
<import>
|
|
779
|
-
Dimension functions are available through the Dim namespace from "@superblocksteam/library".
|
|
780
|
-
|
|
781
|
-
import { Dim } from "@superblocksteam/library";
|
|
782
|
-
</import>
|
|
783
|
-
|
|
784
|
-
Dim is the only way to supply \`width\` and \`height\` properties to components. Do not supply them as strings or numbers. Fallback to Tailwind classes only when necessary.
|
|
785
|
-
|
|
786
|
-
CORRECT:
|
|
787
|
-
\`\`\`tsx
|
|
788
|
-
<Stack width={Dim.fill()} height={Dim.fit()} />
|
|
789
|
-
<Stack width={Dim.percent(100)} height={Dim.px(100)} />
|
|
790
|
-
\`\`\`
|
|
791
|
-
|
|
792
|
-
INCORRECT:
|
|
793
|
-
\`\`\`tsx
|
|
794
|
-
<Stack width="fill" height="fit" />
|
|
795
|
-
<Stack width="100%" height="100px" />
|
|
516
|
+
return (
|
|
517
|
+
<div className="flex flex-col gap-3">
|
|
518
|
+
<Input value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} />
|
|
519
|
+
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
|
|
520
|
+
<SelectTrigger>
|
|
521
|
+
<SelectValue placeholder="Select a category" />
|
|
522
|
+
</SelectTrigger>
|
|
523
|
+
<SelectContent>
|
|
524
|
+
{categories.map((category) => (
|
|
525
|
+
<SelectItem key={category} value={category}>
|
|
526
|
+
{category}
|
|
527
|
+
</SelectItem>
|
|
528
|
+
))}
|
|
529
|
+
</SelectContent>
|
|
530
|
+
</Select>
|
|
531
|
+
|
|
532
|
+
<ProductGrid
|
|
533
|
+
products={products}
|
|
534
|
+
searchTerm={searchQuery}
|
|
535
|
+
category={selectedCategory}
|
|
536
|
+
/>
|
|
537
|
+
</div>
|
|
538
|
+
);
|
|
539
|
+
};
|
|
796
540
|
\`\`\`
|
|
797
541
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
<icons>
|
|
801
|
-
Icons are important for creating visually appealing and professional UIs.
|
|
802
|
-
|
|
803
|
-
CRITICAL: Always use icons rather than emojis. **Never** use emojis unless the explicity requested by the user.
|
|
804
|
-
|
|
805
|
-
<usage>
|
|
806
|
-
You have three ways to add icons to your components:
|
|
807
|
-
|
|
808
|
-
1. **Icon Component**: Use \`<Icon icon="name" />\` where "name" matches any Lucide icon name
|
|
809
|
-
\`\`\`tsx
|
|
810
|
-
<Icon icon="info" />
|
|
811
|
-
<Icon icon="heart" />
|
|
812
|
-
\`\`\`
|
|
813
|
-
|
|
814
|
-
2. **Icon Props**: Pass icon names as strings to components that accept an icon prop
|
|
815
|
-
\`\`\`tsx
|
|
816
|
-
<Badge icon="heart">Badge</Badge>
|
|
817
|
-
<Button icon="plus">Add Item</Button>
|
|
818
|
-
<Button icon={computed(() => errors.value.length > 0 ? "error" : "check")}>Add Item</Button>
|
|
819
|
-
\`\`\`
|
|
820
|
-
</usage>
|
|
821
|
-
|
|
822
|
-
Always use valid Lucide icon names (e.g., "heart", "info", "user", "home", "search").
|
|
823
|
-
|
|
824
|
-
</icons>
|
|
825
|
-
|
|
826
|
-
<apis>
|
|
827
|
-
Superblocks APIs form the backend logic layer of Superblocks applications. They have a specialized format on disk, so you must use the appropriate tools to create and edit them.
|
|
828
|
-
|
|
829
|
-
<creating_and_editing_apis>
|
|
830
|
-
**CRITICAL: Only modify APIs when explicitly requested by the user.**
|
|
831
|
-
|
|
832
|
-
Rules for API modification:
|
|
833
|
-
- If the user tags/mentions an API but says "don't change the API" or "without modifying the API", you MUST NOT call build_generateApiSource
|
|
834
|
-
- If the user tags an API for context/reference only, do not modify it
|
|
835
|
-
- Only delegate to the API tool when the user explicitly requests API creation or modification
|
|
836
|
-
- When the user does request changes to multiple APIs, parallelize the tool calls to optimize the process
|
|
837
|
-
|
|
838
|
-
Examples of when NOT to modify APIs:
|
|
839
|
-
- "The getUsersApi returns this data [tagged API] but don't change it"
|
|
840
|
-
- "Using the existing searchApi [tagged], update the UI to show results"
|
|
841
|
-
- "The API is working fine, just fix the display"
|
|
842
|
-
|
|
843
|
-
Examples of when TO modify APIs:
|
|
844
|
-
- "Update the getUsersApi to include email addresses"
|
|
845
|
-
- "Create a new API to fetch products"
|
|
846
|
-
- "Change the searchApi to filter by category"
|
|
847
|
-
</creating_and_editing_apis>
|
|
542
|
+
### Best Practices
|
|
848
543
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
\`\`\`tsx
|
|
854
|
-
const { myApiName } = Page1;
|
|
855
|
-
\`\`\`
|
|
544
|
+
- ✅ Always use \`key\` prop when rendering lists
|
|
545
|
+
- ✅ Emit meaningful payloads on events (new value, row data, etc.)
|
|
546
|
+
- ✅ Filter/transform data inside components
|
|
547
|
+
</custom_components>
|
|
856
548
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
<Table data={computed(() => myApiName.response)} />
|
|
860
|
-
\`\`\`
|
|
549
|
+
<design_system_guidance>
|
|
550
|
+
## Tailwind CSS v4 Design System
|
|
861
551
|
|
|
862
|
-
|
|
552
|
+
**All design tokens are defined in \`index.css\`**. Apps come with a professional black-and-white theme by default.
|
|
863
553
|
|
|
554
|
+
### Semantic Tokens Rule
|
|
555
|
+
**ALWAYS use semantic tokens** (\`bg-background\`, \`text-foreground\`, \`border-border\`, \`shadow-card\`) — NEVER raw Tailwind utilities (\`bg-white\`, \`text-black\`, \`shadow-lg\`).
|
|
864
556
|
|
|
865
|
-
✅ CORRECT
|
|
866
|
-
|
|
867
|
-
<Table data={computed(() => myApiName.response)} />
|
|
868
|
-
\`\`\`
|
|
557
|
+
✅ CORRECT: \`<Card className="bg-background text-foreground border border-border" />\`
|
|
558
|
+
❌ WRONG: \`<Card className="bg-white text-black border-gray-200" />\`
|
|
869
559
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
560
|
+
### When to Modify index.css
|
|
561
|
+
Only modify \`index.css\` when:
|
|
562
|
+
- User explicitly requests branding/theme changes
|
|
563
|
+
- Replicating a specific brand look (e.g., Yelp, Instacart)
|
|
564
|
+
- Feature requests (lists, filters, CRUD) do NOT require changes
|
|
874
565
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
566
|
+
**Modification Rules:**
|
|
567
|
+
- All colors must be in OKLCH format
|
|
568
|
+
- Use semantic names (\`--color-warning\`, \`--shadow-elevated\`)
|
|
569
|
+
- Do not remove or rename existing tokens
|
|
570
|
+
- Limit color palette to 5 colors max (1 primary, 2-3 neutrals, 1-2 accents)
|
|
571
|
+
- Avoid gradients unless explicitly requested
|
|
572
|
+
- Minimal font sizes (3 max: body, section heading, main heading)
|
|
879
573
|
|
|
880
|
-
|
|
574
|
+
### Component Variants
|
|
575
|
+
Create reusable component variants instead of one-off styles:
|
|
881
576
|
|
|
882
|
-
To have the API run when the page loads, use the onLoad property on Page.
|
|
883
577
|
\`\`\`tsx
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
height={Dim.fill()}
|
|
887
|
-
width={Dim.fill()}
|
|
888
|
-
onLoad={EventFlow.runApis([myApiName])}
|
|
889
|
-
>
|
|
890
|
-
{/* Page content */}
|
|
891
|
-
</Page>
|
|
892
|
-
\`\`\`
|
|
893
|
-
|
|
894
|
-
IMPORTANT: API's can access binding and state variables directly as READ ONLY in their source code. When you need input for an API, keep an APIs input interface in mind when creating components and state variables.
|
|
895
|
-
|
|
896
|
-
For example, if an API requires a search input:
|
|
897
|
-
\`\`\`typescript
|
|
898
|
-
// in my API source code
|
|
899
|
-
const query = userIdSearchInput.value;
|
|
900
|
-
\`\`\`
|
|
578
|
+
// ❌ WRONG - Hacky inline overrides
|
|
579
|
+
<Button className="text-white border-white hover:bg-white" />
|
|
901
580
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
// in my page source code
|
|
905
|
-
<Input bind={userIdSearchInput} />
|
|
581
|
+
// ✅ CORRECT - Use or create a variant
|
|
582
|
+
<Button variant="secondary" />
|
|
906
583
|
\`\`\`
|
|
907
584
|
|
|
908
|
-
|
|
909
|
-
**CRITICAL: API responses are untrusted runtime data. TypeScript types are compile-time only.**
|
|
585
|
+
Variants use semantic tokens and are defined in component files using \`cva()\`.
|
|
910
586
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
- ALWAYS provide fallback values for display
|
|
914
|
-
- NEVER trust that TypeScript-required fields will exist at runtime
|
|
587
|
+
## Component System
|
|
588
|
+
All base UI components in the \`components/ui\` directory are Shadcn/Radix UI components and adhere to the Shadcn design system.
|
|
915
589
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
❌ **UNSAFE** - Will crash if user is null/undefined:
|
|
919
|
-
\`\`\`typescript
|
|
920
|
-
<Typography text={\`opened by \${props.pr.user.login}\`} />
|
|
921
|
-
\`\`\`
|
|
922
|
-
|
|
923
|
-
✅ **SAFE** - Handles missing data gracefully:
|
|
924
|
-
\`\`\`typescript
|
|
925
|
-
<Typography text={computed(() =>
|
|
926
|
-
\`opened by \${props.pr?.user?.login || 'Unknown'}\`
|
|
927
|
-
)} />
|
|
928
|
-
\`\`\`
|
|
929
|
-
|
|
930
|
-
**Why this matters:**
|
|
931
|
-
- API data can have null/undefined fields even if types say they're required
|
|
932
|
-
- Components render before data is fully loaded
|
|
933
|
-
- Deleted accounts, deactivated users, partial responses are common
|
|
934
|
-
- Better to show "Unknown" than crash the app
|
|
935
|
-
|
|
936
|
-
**Apply this principle to ALL API response data, especially:**
|
|
937
|
-
- User objects (can be null for deleted accounts)
|
|
938
|
-
- Nested arrays (can be empty or undefined during loading)
|
|
939
|
-
- Optional fields in API specs
|
|
940
|
-
- Fields from third-party APIs (GitHub, Stripe, etc.)
|
|
941
|
-
</defensive_api_data_access>
|
|
942
|
-
|
|
943
|
-
</using_apis>
|
|
944
|
-
|
|
945
|
-
<api_state_updates>
|
|
946
|
-
## 🚨 CRITICAL: Frontend/Backend Separation for State Updates
|
|
947
|
-
|
|
948
|
-
**APIs are backend workflows that can only READ state. State mutations MUST happen on the frontend through EventFlow.**
|
|
949
|
-
|
|
950
|
-
### The Rule:
|
|
951
|
-
- **Backend (API files)**: Read state, process data, return results
|
|
952
|
-
- **Frontend (scope.ts/pages)**: Update state based on API results using onSuccess/onError handlers
|
|
953
|
-
- **Other frontend state updates**: Can happen anywhere via EventFlow (button clicks, input changes, etc.)
|
|
954
|
-
|
|
955
|
-
### ❌ WRONG: APIs CANNOT set state variables
|
|
956
|
-
APIs cannot modify StateVars or component properties. They only return data.
|
|
957
|
-
|
|
958
|
-
### ✅ CORRECT: For API-triggered state updates, use onSuccess/onError handlers in scope
|
|
959
|
-
When registering APIs in the scope file, use onSuccess/onError to handle state updates based on API results:
|
|
960
|
-
|
|
961
|
-
\`\`\`tsx
|
|
962
|
-
// In scope.ts - registering the API with state update handlers
|
|
963
|
-
export const Page1Scope = createScope(({ entities: { createUserApi, fetchDataApi }}) => ({
|
|
964
|
-
// ... other entities ...
|
|
965
|
-
|
|
966
|
-
createUserApi: {
|
|
967
|
-
onSuccess: EventFlow.runJS(() => {
|
|
968
|
-
// Update state based on API response
|
|
969
|
-
userNameVar.value = createUserApi.response.name;
|
|
970
|
-
userListVar.value = [...userListVar.value, createUserApi.response];
|
|
971
|
-
isLoadingVar.value = false;
|
|
972
|
-
CreateUserDialog.isOpen = false;
|
|
973
|
-
|
|
974
|
-
toast.success("User created successfully!");
|
|
975
|
-
}),
|
|
976
|
-
onError: EventFlow.runJS(() => {
|
|
977
|
-
isLoadingVar.value = false;
|
|
978
|
-
toast.error("Failed to create user");
|
|
979
|
-
})
|
|
980
|
-
},
|
|
981
|
-
|
|
982
|
-
fetchDataApi: {
|
|
983
|
-
onSuccess: EventFlow.setStateVar(dataVar, {
|
|
984
|
-
value: computed(() => fetchDataApi.response)
|
|
985
|
-
})
|
|
986
|
-
}
|
|
987
|
-
}));
|
|
988
|
-
\`\`\`
|
|
989
|
-
|
|
990
|
-
### Common Patterns:
|
|
991
|
-
|
|
992
|
-
**Loading states:**
|
|
993
|
-
\`\`\`tsx
|
|
994
|
-
// Use the built-in .isLoading property
|
|
995
|
-
<Typography text={computed(() => searchApi.isLoading ? "Searching..." : "Search complete")} />
|
|
996
|
-
<Button isDisabled={computed(() => searchApi.isLoading)} text="Search" />
|
|
997
|
-
<Spinner isVisible={computed(() => searchApi.isLoading)} />
|
|
998
|
-
|
|
999
|
-
// In scope.ts - onSuccess/onError for result handling
|
|
1000
|
-
searchApi: {
|
|
1001
|
-
onSuccess: EventFlow.runJS(() => {
|
|
1002
|
-
searchResultsVar.value = searchApi.response;
|
|
1003
|
-
}),
|
|
1004
|
-
onError: EventFlow.runJS(() => {
|
|
1005
|
-
searchResultsVar.value = [];
|
|
1006
|
-
toast.error("Search failed");
|
|
1007
|
-
})
|
|
1008
|
-
}
|
|
1009
|
-
\`\`\`
|
|
1010
|
-
|
|
1011
|
-
**Form submissions:**
|
|
1012
|
-
\`\`\`tsx
|
|
1013
|
-
// In scope.ts
|
|
1014
|
-
submitFormApi: {
|
|
1015
|
-
onSuccess: EventFlow
|
|
1016
|
-
.runJS(() => {
|
|
1017
|
-
submittedDataVar.value = submitFormApi.response;
|
|
1018
|
-
toast.success("Form submitted!");
|
|
1019
|
-
})
|
|
1020
|
-
.resetComponent(FormInput1)
|
|
1021
|
-
.resetComponent(FormInput2)
|
|
1022
|
-
.setComponentProperty(FormDialog, { property: "isOpen", value: false })
|
|
1023
|
-
}
|
|
1024
|
-
\`\`\`
|
|
1025
|
-
|
|
1026
|
-
**Data refresh after mutation:**
|
|
1027
|
-
\`\`\`tsx
|
|
1028
|
-
// In scope.ts
|
|
1029
|
-
deleteItemApi: {
|
|
1030
|
-
onSuccess: EventFlow
|
|
1031
|
-
.runJS(() => {
|
|
1032
|
-
toast.success("Item deleted");
|
|
1033
|
-
})
|
|
1034
|
-
.runApis([fetchItemsApi]) // Refresh the list
|
|
1035
|
-
}
|
|
1036
|
-
\`\`\`
|
|
1037
|
-
|
|
1038
|
-
### Why this separation matters for APIs:
|
|
1039
|
-
- Keeps data flow unidirectional and predictable
|
|
1040
|
-
- APIs remain pure data processors
|
|
1041
|
-
- API-triggered state updates are centralized in onSuccess/onError handlers
|
|
1042
|
-
- Makes debugging and testing easier
|
|
1043
|
-
- Aligns with the Superblocks visual editor model
|
|
1044
|
-
</api_state_updates>
|
|
1045
|
-
</apis>
|
|
1046
|
-
|
|
1047
|
-
<superblocks_state>
|
|
1048
|
-
|
|
1049
|
-
The Superblocks state system is the primary mechanism for managing application state. It is declarative, consistent, and makes debugging easier.
|
|
1050
|
-
Entities like components, APIs, and StateVars are all part of this system.
|
|
1051
|
-
|
|
1052
|
-
**Important:** All state in Superblocks is **session-only**.
|
|
1053
|
-
|
|
1054
|
-
---
|
|
1055
|
-
|
|
1056
|
-
<state_usage_hierarchy>
|
|
1057
|
-
## State Usage Hierarchy
|
|
1058
|
-
|
|
1059
|
-
Follow this decision tree when managing state:
|
|
1060
|
-
|
|
1061
|
-
1. **Component bindings** → Always prefer reading directly from component bindings
|
|
1062
|
-
- Example: <Input bind={FirstNameInput} /> → FirstNameInput.value
|
|
1063
|
-
|
|
1064
|
-
2. **APIs** → Use \\\`.response\\\` directly for API data
|
|
1065
|
-
- Example: <Table data={computed(() => getUsersApi.response)} />
|
|
1066
|
-
|
|
1067
|
-
3. **StateVars** → Use only when you need additional, derived, or snapshotted state
|
|
1068
|
-
- Examples:
|
|
1069
|
-
- Derived data from multiple inputs
|
|
1070
|
-
- Multi-step form state
|
|
1071
|
-
- Snapshot/undo/history
|
|
1072
|
-
- Persisted app state across views
|
|
1073
|
-
|
|
1074
|
-
3.5 **If you feel tempted to create a top-level \`const\` for reuse → stop.** Instead, create a **StateVar with a computed default** and reference \`myVar.value\` inline where needed.
|
|
1075
|
-
|
|
1076
|
-
❌ Do **not** mirror component or API state into StateVars unless you need a snapshot or are deriving values from multiple sources.
|
|
1077
|
-
</state_usage_hierarchy>
|
|
1078
|
-
|
|
1079
|
-
<state_red_flags>
|
|
1080
|
-
## Red Flags (Stop and Fix)
|
|
1081
|
-
|
|
1082
|
-
- Starting a plan by adding **StateVars for inputs/filters** (e.g., \`selectedCityVar\`, \`dateRangeVar\`) **before** placing/binding those inputs.
|
|
1083
|
-
- Creating a StateVar whose value **duplicates a binding** (e.g., \`UserInput.value → inputVar.value\`).
|
|
1084
|
-
- Mirroring **API \`.response\`** into a StateVar just to pass to props.
|
|
1085
|
-
</state_red_flags>
|
|
1086
|
-
|
|
1087
|
-
---
|
|
1088
|
-
|
|
1089
|
-
<access_patterns>
|
|
1090
|
-
## Access Patterns
|
|
1091
|
-
|
|
1092
|
-
| Context | How to Access |
|
|
1093
|
-
|------------------------|--------------------------------------------|
|
|
1094
|
-
| JSX props | computed(() => var.value) |
|
|
1095
|
-
| runJS | var.value (direct) |
|
|
1096
|
-
| EventFlow parameters | computed(() => var.value) |
|
|
1097
|
-
| Component binding | computed(() => ComponentName.property) or inside runJS |
|
|
1098
|
-
|
|
1099
|
-
**Rules:**
|
|
1100
|
-
- Never access binding properties directly in JSX without \`computed\`.
|
|
1101
|
-
- Use \`computed\` for any **dynamic property values** in EventFlow chains (e.g. \`navigateTo\`, etc.).
|
|
1102
|
-
- Access raw \`var.value\` directly only inside \`runJS\`.
|
|
1103
|
-
- Never lift computed logic to a top-level \`const\` for view usage. Inline on the prop or add a StateVar with a computed default value
|
|
1104
|
-
- EventFlow arguments that are dynamic must use \`computed(...)\` (e.g., \`navigateTo(computed(() => ...))\`).
|
|
1105
|
-
- Handlers belong inline in the prop: \`onClick={EventFlow.runJS(...)}\`
|
|
1106
|
-
</access_patterns>
|
|
1107
|
-
|
|
1108
|
-
<component_state_bindings>
|
|
1109
|
-
## Component State Bindings
|
|
1110
|
-
|
|
1111
|
-
Bindings give you named references to component instances so you can **read** (and, for input-like or control props, **write**) their state. Add a bind to any component instance you will reference.
|
|
1112
|
-
|
|
1113
|
-
### Correct
|
|
1114
|
-
\`\`\`tsx
|
|
1115
|
-
<Input bind={FirstNameInput} />
|
|
1116
|
-
<Typography text={computed(() => \`First name: \${FirstNameInput.value}\`)} />
|
|
1117
|
-
\`\`\`
|
|
1118
|
-
|
|
1119
|
-
### Incorrect
|
|
1120
|
-
\`\`\`tsx
|
|
1121
|
-
<Input bind={FirstNameInput} />
|
|
1122
|
-
<Typography text={FirstNameInput.value} /> // ❌ Missing computed in JSX
|
|
1123
|
-
\`\`\`
|
|
1124
|
-
|
|
1125
|
-
**Key rules**
|
|
1126
|
-
- Bind each component once. You cannot share bindings.
|
|
1127
|
-
- Never pass bindings between components.
|
|
1128
|
-
- In JSX, read via \`computed(...)\`; in handlers, read directly in \`EventFlow.runJS\`.
|
|
1129
|
-
- Do **not** mirror bindings into StateVars unless you need a one-time **snapshot** (history/persistence).
|
|
1130
|
-
- Do not use the same binding name as the component's import name (e.g., if the component is imported as Button, the binding cannot be Button). Instead, generate a distinct, descriptive, and unique binding name that avoids collisions with imported names.
|
|
1131
|
-
- IMPORTANT: Take API input interfaces into account when creating bindings! If there are no suitable bindings, then create a state variable or hard-code the value. Without a reference to a binding, state variable, or hard-coded value, the API will not work!
|
|
1132
|
-
- IMPORTANT: Take API output interfaces into account when writing frontent code! Do not make up API output interfaces or properties, as they may not exist!
|
|
1133
|
-
|
|
1134
|
-
---
|
|
1135
|
-
|
|
1136
|
-
<writing_to_component_properties>
|
|
1137
|
-
## Writing to Component Properties — Policy
|
|
1138
|
-
|
|
1139
|
-
Decide with this test: **“Can the end-user directly modify this value directly during the app lifecycle?”**
|
|
1140
|
-
We distinguish **Input-like**, **Data/derived**, and **Control** props.
|
|
1141
|
-
|
|
1142
|
-
- **INPUT-LIKE** (user-edited values)
|
|
1143
|
-
Examples: \`Input.value\`, \`Select.selected\`, \`Switch.checked\`, \`DatePicker.value\`
|
|
1144
|
-
- Marked **\`readAndWrite\`** in the component definition.
|
|
1145
|
-
- JSX provides the **default** (often via a \`default*\` prop that seeds the live \`value\`).
|
|
1146
|
-
- **Live source of truth** is the user's edits.
|
|
1147
|
-
- **Imperative writes are rare & intentional** (reset/clear, apply suggestion). Prefer declarative props otherwise.
|
|
1148
|
-
|
|
1149
|
-
- **DATA / DERIVED / DISPLAY** (driven by app data, APIs, or other props)
|
|
1150
|
-
Examples: \`Typography.value\`, \`Table.rows\`, \`Chart.series\`, \`Avatar.src\`
|
|
1151
|
-
- Treat as **declarative** and **reactive** only.
|
|
1152
|
-
- **Never** set imperatively; pass via JSX with \`computed(...)\` so updates flow automatically.
|
|
1153
|
-
|
|
1154
|
-
- **CONTROL** (visibility/toggle state for dialogs, sheets, modals, drawers, etc.)
|
|
1155
|
-
Examples: \`Dialog1.isOpen\`, \`Sheet1.isOpen\`
|
|
1156
|
-
- Not user-typed and not derived data; callers **control** them.
|
|
1157
|
-
- **Imperative writes are the standard pattern** for opening/closing:
|
|
1158
|
-
\`\`\`tsx
|
|
1159
|
-
<Button
|
|
1160
|
-
onClick={EventFlow.setComponentProperty(Dialog1, {
|
|
1161
|
-
property: "isOpen",
|
|
1162
|
-
value: true,
|
|
1163
|
-
})}
|
|
1164
|
-
>
|
|
1165
|
-
Open Dialog
|
|
1166
|
-
</Button>
|
|
1167
|
-
\`\`\`
|
|
1168
|
-
|
|
1169
|
-
**Thumb rule:** If the user can't type/select it and it's not data, ask: “Is it a UI control state (open/close/toggle)?” If yes, callers set it imperatively; otherwise bind it declaratively.
|
|
1170
|
-
</writing_to_component_properties>
|
|
1171
|
-
|
|
1172
|
-
---
|
|
1173
|
-
|
|
1174
|
-
<setting_component_properties>
|
|
1175
|
-
## Setting Component Properties
|
|
1176
|
-
|
|
1177
|
-
**Preferred:** Pass dynamic values in JSX with \`computed(...)\`.
|
|
1178
|
-
**Exceptions:**
|
|
1179
|
-
- Imperative writes for **input-like** props when overwriting user input is explicitly desired.
|
|
1180
|
-
- Imperative writes for **control** props (open/close) to drive UI affordances.
|
|
1181
|
-
|
|
1182
|
-
### ✅ Declarative data passed as props in JSX (preferred)
|
|
1183
|
-
\`\`\`tsx
|
|
1184
|
-
// Reactive table data
|
|
1185
|
-
<Table bind={OrdersTable} data={computed(() => orders.filter(o => o.status === StatusFilter.value))} />
|
|
1186
|
-
\`\`\`
|
|
1187
|
-
|
|
1188
|
-
### ❌ Imperative on data/derived props (don't do this)
|
|
1189
|
-
\`\`\`tsx
|
|
1190
|
-
// ❌ Breaks reactivity/debugging; hides the dataflow
|
|
1191
|
-
<Button onClick={EventFlow.runJS(() => {
|
|
1192
|
-
OrdersTable.data = orders.filter(o => o.status === StatusFilter.value);
|
|
1193
|
-
})} />
|
|
1194
|
-
\`\`\`
|
|
1195
|
-
|
|
1196
|
-
### ✅ Imperative overwrite of **input-like** value (rare but OK)
|
|
1197
|
-
\`\`\`tsx
|
|
1198
|
-
// "Use AI suggestion" overwrites an input the user controls
|
|
1199
|
-
<Button
|
|
1200
|
-
onClick={EventFlow.setComponentProperty(DescriptionInput, {
|
|
1201
|
-
property: "value",
|
|
1202
|
-
value: computed(() => generateDescriptionAPI.response?.description ?? ""),
|
|
1203
|
-
})}
|
|
1204
|
-
>
|
|
1205
|
-
Use Suggestion
|
|
1206
|
-
</Button>
|
|
1207
|
-
\`\`\`
|
|
1208
|
-
|
|
1209
|
-
### ✅ Imperative control of **open/close** (dialogs/sheets)
|
|
1210
|
-
\`\`\`tsx
|
|
1211
|
-
<Button
|
|
1212
|
-
onClick={EventFlow.setComponentProperty(Sheet1, {
|
|
1213
|
-
property: "isOpen",
|
|
1214
|
-
value: true,
|
|
1215
|
-
})}
|
|
1216
|
-
>
|
|
1217
|
-
Open sheet
|
|
1218
|
-
</Button>
|
|
1219
|
-
|
|
1220
|
-
\`\`\`
|
|
1221
|
-
|
|
1222
|
-
### ✅ Complex input overwrite (still input-like)
|
|
1223
|
-
\`\`\`tsx
|
|
1224
|
-
import { toast } from "sonner";
|
|
1225
|
-
<Button
|
|
1226
|
-
onClick={EventFlow.runJS(() => {
|
|
1227
|
-
const list = descriptionGeneratorAPI.response?.descriptions ?? [];
|
|
1228
|
-
const valid = list.filter(d => typeof d === "string" && d.trim() !== "");
|
|
1229
|
-
if (valid.length === 0) { toast("No valid descriptions."); return; }
|
|
1230
|
-
const i = Math.floor(Math.random() * valid.length);
|
|
1231
|
-
DescriptionInput.value = valid[i]; // explicit overwrite of an input prop
|
|
1232
|
-
toast("Random description selected!");
|
|
1233
|
-
})}
|
|
1234
|
-
>
|
|
1235
|
-
Pick random description
|
|
1236
|
-
</Button>
|
|
1237
|
-
\`\`\`
|
|
1238
|
-
|
|
1239
|
-
<resetting_component_properties>
|
|
1240
|
-
## Resetting Component Properties
|
|
1241
|
-
|
|
1242
|
-
**Use reset often for filters & forms.** It is **more common and preferable** to imperatively writing empty strings because reset returns inputs to their **defined defaults** (including non‑empty defaults).
|
|
1243
|
-
|
|
1244
|
-
\`\`\`tsx
|
|
1245
|
-
// Reset entire component to defaults (common in forms/filter bars)
|
|
1246
|
-
<Button onClick={EventFlow.resetComponent(Input1)} />
|
|
1247
|
-
|
|
1248
|
-
// Reset a specific input property to its default
|
|
1249
|
-
<Button onClick={EventFlow.resetComponent(Input1, { property: "value" })} />
|
|
1250
|
-
\`\`\`
|
|
1251
|
-
|
|
1252
|
-
You do **not** reset data/derived props (e.g., \`Sheet1.data\`, \`Table1.rows\`): those update via declarative bindings.
|
|
1253
|
-
</resetting_component_properties>
|
|
1254
|
-
</component_state_bindings>
|
|
1255
|
-
|
|
1256
|
-
<api_state>
|
|
1257
|
-
## API State
|
|
1258
|
-
|
|
1259
|
-
APIs expose three properties:
|
|
1260
|
-
- .response → Most recent response (success)
|
|
1261
|
-
- .error → Most recent error
|
|
1262
|
-
- .isLoading → Boolean indicating if the API is currently executing
|
|
1263
|
-
|
|
1264
|
-
Rules:
|
|
1265
|
-
- Always use .response directly in computed or runJS.
|
|
1266
|
-
- Use .isLoading to show loading states in UI.
|
|
1267
|
-
- Do not mirror API data into StateVars unless snapshotting or persisting.
|
|
1268
|
-
|
|
1269
|
-
✅ Correct:
|
|
1270
|
-
\\\`\\\`\\\`tsx
|
|
1271
|
-
<Table data={computed(() => getUsersApi.response)} />
|
|
1272
|
-
<Typography text={computed(() => getUsersApi.isLoading ? "Loading..." : "Ready")} />
|
|
1273
|
-
<Button isDisabled={computed(() => getUsersApi.isLoading)} />
|
|
1274
|
-
\\\`\\\`\\\`
|
|
1275
|
-
|
|
1276
|
-
❌ Incorrect:
|
|
1277
|
-
\\\`\\\`\\\`tsx
|
|
1278
|
-
<Table data={computed(() => myApi.response.myStep.output)} /> // step access not allowed
|
|
1279
|
-
<Typography text={computed(() => getUsersApi.loading)} /> // use .isLoading, not .loading
|
|
1280
|
-
\\\`\\\`\\\`
|
|
1281
|
-
</api_state>
|
|
1282
|
-
|
|
1283
|
-
---
|
|
1284
|
-
|
|
1285
|
-
<state_vars>
|
|
1286
|
-
## State Variables (StateVars)
|
|
1287
|
-
|
|
1288
|
-
StateVars store additional application state outside of components and APIs.
|
|
1289
|
-
Always use .value to read/write.
|
|
1290
|
-
|
|
1291
|
-
### Examples
|
|
1292
|
-
- Derived/filter data from multiple inputs
|
|
1293
|
-
- Multi-step form progress
|
|
1294
|
-
- Last visited page
|
|
1295
|
-
- Snapshots of component/API state for undo/history
|
|
1296
|
-
- Dialog open state
|
|
1297
|
-
|
|
1298
|
-
\\\`\\\`\\\`tsx
|
|
1299
|
-
<Typography text={computed(() => \\\`Count: \${counterVar.value}\\\`)} />
|
|
1300
|
-
\\\`\\\`\\\`
|
|
1301
|
-
|
|
1302
|
-
<setting_state_vars>
|
|
1303
|
-
## Setting StateVars
|
|
1304
|
-
|
|
1305
|
-
- **Simple updates** → use EventFlow.setStateVar
|
|
1306
|
-
- **Complex operations** → use EventFlow.runJS
|
|
1307
|
-
|
|
1308
|
-
\\\`\\\`\\\`tsx
|
|
1309
|
-
// Simple
|
|
1310
|
-
<Button onClick={EventFlow.setStateVar(counterVar, { value: computed(() => counterVar.value + 1) })} />
|
|
1311
|
-
|
|
1312
|
-
// Complex
|
|
1313
|
-
<Button onClick={EventFlow.runJS(() => {
|
|
1314
|
-
counterVar.value++;
|
|
1315
|
-
itemsVar.value = [...itemsVar.value, { id: Date.now() }];
|
|
1316
|
-
})} />
|
|
1317
|
-
\\\`\\\`\\\`
|
|
1318
|
-
</setting_state_vars>
|
|
1319
|
-
|
|
1320
|
-
<state_vars_default_values>
|
|
1321
|
-
## Default Values for StateVars
|
|
1322
|
-
|
|
1323
|
-
- **Static defaults** → Use when the value will be modified later (like let in JS).
|
|
1324
|
-
- **Computed defaults** → Use when the value should be derived reactively and generally *not* overridden during the app lifecycle.
|
|
1325
|
-
|
|
1326
|
-
While it is valid to use a computed default as an initial derived value that may later be updated, you should be intentional as this is not the common case.
|
|
1327
|
-
|
|
1328
|
-
</state_vars_default_values>
|
|
1329
|
-
|
|
1330
|
-
</state_vars>
|
|
1331
|
-
|
|
1332
|
-
</superblocks_state>
|
|
1333
|
-
|
|
1334
|
-
<events>
|
|
1335
|
-
EventFlow is Superblocks' declarative event handling system that completely replaces standard React event handlers. Instead of writing imperative JavaScript functions, you chain predefined actions that the platform executes.
|
|
1336
|
-
|
|
1337
|
-
<comparison_to_react_event_handlers>
|
|
1338
|
-
\`\`\`tsx
|
|
1339
|
-
// Standard React ❌
|
|
1340
|
-
<button onClick={(e) => {
|
|
1341
|
-
setCounter(counter + 1);
|
|
1342
|
-
fetchData();
|
|
1343
|
-
showNotification("Updated!");
|
|
1344
|
-
}}>
|
|
1345
|
-
|
|
1346
|
-
// Superblocks EventFlow ✅
|
|
1347
|
-
import { toast } from "sonner";
|
|
1348
|
-
<Button onClick={EventFlow
|
|
1349
|
-
.setStateVar(counterVar, { value: computed(() => counterVar.value + NumberInput1.value) })
|
|
1350
|
-
.runApis([fetchDataApi])
|
|
1351
|
-
.runJS(() => {
|
|
1352
|
-
toast.success("Updated!")
|
|
1353
|
-
})
|
|
1354
|
-
} />
|
|
1355
|
-
\`\`\`
|
|
1356
|
-
</comparison_to_react_event_handlers>
|
|
1357
|
-
|
|
1358
|
-
<builder_pattern_architecture>
|
|
1359
|
-
EventFlow uses method chaining to compose sequences of actions:
|
|
1360
|
-
|
|
1361
|
-
\`\`\`tsx
|
|
1362
|
-
EventFlow
|
|
1363
|
-
.action1(params)
|
|
1364
|
-
.action2(params)
|
|
1365
|
-
.action3(params)
|
|
1366
|
-
// No .run() needed - automatically executes
|
|
1367
|
-
\`\`\`
|
|
1368
|
-
</builder_pattern_architecture>
|
|
1369
|
-
|
|
1370
|
-
<built_in_action_types>
|
|
1371
|
-
### State Management
|
|
1372
|
-
- \`EventFlow.setStateVar(counterVar, { value: computed(() => counterVar.value + NumberInput1.value) })\` - Update state variable
|
|
1373
|
-
- \`EventFlow.setQueryParams({ key: "value" }, { keep: true })\` - Update URL query parameters
|
|
1374
|
-
|
|
1375
|
-
### API Operations
|
|
1376
|
-
- \`EventFlow.runApis([api1, api2])\` - Execute APIs in parallel
|
|
1377
|
-
- Success/error handling: \`EventFlow.runApis([api], { onSuccess: EventFlow..., onError: EventFlow... })\`
|
|
1378
|
-
|
|
1379
|
-
### UI Control
|
|
1380
|
-
- \`EventFlow.navigateTo("/path", { newWindow: false })\` - Navigation
|
|
1381
|
-
- \`EventFlow.runJS(() => { toast.<type>("message") })\` - Show notifications
|
|
1382
|
-
|
|
1383
|
-
### Custom Logic
|
|
1384
|
-
- \`EventFlow.runJS(() => { /* JavaScript code */ })\` - Execute arbitrary JavaScript
|
|
1385
|
-
|
|
1386
|
-
</built_in_action_types>
|
|
1387
|
-
|
|
1388
|
-
<two_tier_system>
|
|
1389
|
-
### Tier 1: Declarative Actions (Preferred)
|
|
1390
|
-
Use built-in EventFlow methods for common operations:
|
|
1391
|
-
\`\`\`tsx
|
|
1392
|
-
EventFlow.setStateVar(userVar, { value: computed(() => {
|
|
1393
|
-
const raw = usernameInputVar.value ?? "";
|
|
1394
|
-
return raw.trim().toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1395
|
-
}) })
|
|
1396
|
-
EventFlow.runApis([saveUserApi])
|
|
1397
|
-
EventFlow.runJS(() => {
|
|
1398
|
-
toast.success("Saved!")
|
|
1399
|
-
})
|
|
1400
|
-
\`\`\`
|
|
1401
|
-
|
|
1402
|
-
### Tier 2: Imperative Logic (When Needed)
|
|
1403
|
-
Use \`EventFlow.runJS()\` for complex logic that can't be expressed declaratively:
|
|
1404
|
-
\`\`\`tsx
|
|
1405
|
-
EventFlow.runJS(() => {
|
|
1406
|
-
// Complex calculations, loops, conditions
|
|
1407
|
-
if (userVar.value.role === 'admin') {
|
|
1408
|
-
adminPanelVar.value = true;
|
|
1409
|
-
auditLogVar.value.push({
|
|
1410
|
-
action: 'admin_login',
|
|
1411
|
-
timestamp: new Date().toISOString()
|
|
1412
|
-
});
|
|
1413
|
-
}
|
|
1414
|
-
})
|
|
1415
|
-
\`\`\`
|
|
1416
|
-
|
|
1417
|
-
</two_tier_system>
|
|
1418
|
-
|
|
1419
|
-
<key_constraints>
|
|
1420
|
-
**CRITICAL**: ALL components using EventFlow must be wrapped in registerComponent(). See custom_component_registration.
|
|
1421
|
-
|
|
1422
|
-
### ❌ What You Cannot Do
|
|
1423
|
-
- No EventFlow inside runJS: Can't nest EventFlow calls within runJS blocks
|
|
1424
|
-
- No standard event handlers: onClick must use EventFlow, not functions
|
|
1425
|
-
- No async/await: Use APIs and success/error handlers instead
|
|
1426
|
-
- No direct DOM manipulation: Use component properties and state
|
|
1427
|
-
|
|
1428
|
-
### ✅ What You Can Do
|
|
1429
|
-
- Access scope entities directly in runJS: Variables and bound components available by name
|
|
1430
|
-
- Chain multiple actions: Build complex workflows declaratively
|
|
1431
|
-
- Conditional execution: Use success/error handlers for branching logic
|
|
1432
|
-
- Global state access: Import and use Global, Env, etc. in runJS
|
|
1433
|
-
</key_constraints>
|
|
1434
|
-
|
|
1435
|
-
\`\`\`tsx
|
|
1436
|
-
EventFlow.runJS(() => {
|
|
1437
|
-
// ✅ Scope entities accessible directly
|
|
1438
|
-
myStateVar.value = newValue;
|
|
1439
|
-
boundComponent.isVisible = false;
|
|
1440
|
-
|
|
1441
|
-
// ✅ Global state with imports
|
|
1442
|
-
if (Global.user?.groups.some(g => g.name === 'admin')) {
|
|
1443
|
-
adminFeatures.value = true;
|
|
1444
|
-
}
|
|
1445
|
-
})
|
|
1446
|
-
\`\`\`
|
|
1447
|
-
|
|
1448
|
-
The goal is to handle the majority of use cases with declarative actions, falling back to runJS only when the built-in actions aren't sufficient.
|
|
1449
|
-
</state_access_patterns>
|
|
1450
|
-
|
|
1451
|
-
<importing_eventflow>
|
|
1452
|
-
import { EventFlow } from "@superblocksteam/library";
|
|
1453
|
-
</importing_eventflow>
|
|
1454
|
-
|
|
1455
|
-
<event_flow_related_types>
|
|
1456
|
-
export type ComputedProperty<T, ARGS extends any[] = any[]> = {
|
|
1457
|
-
__isComputedProperty: true;
|
|
1458
|
-
value: (scope: any, ...args: ARGS) => T | undefined;
|
|
1459
|
-
};
|
|
1460
|
-
|
|
1461
|
-
export type EntityOutputProp = Exclude<
|
|
1462
|
-
any,
|
|
1463
|
-
boolean | object | bigint | any[],
|
|
1464
|
-
BindingString | EntityDerivedProp<any[]>
|
|
1465
|
-
>;
|
|
1466
|
-
|
|
1467
|
-
export type EntityDerivedProp<ARGS extends any[] = any[]> = (
|
|
1468
|
-
this: Readonly<Entity>,
|
|
1469
|
-
state?: Readonly<ScopedState>,
|
|
1470
|
-
...args: ARGS
|
|
1471
|
-
) => EntityOutputProp;
|
|
1472
|
-
|
|
1473
|
-
export type ValueInputProp<T = EntityOutputProp, ARGS extends any[] = any[]> =
|
|
1474
|
-
| T
|
|
1475
|
-
| EntityDerivedProp
|
|
1476
|
-
| BindingString
|
|
1477
|
-
| ComputedProperty<T, ARGS>;
|
|
1478
|
-
|
|
1479
|
-
export type EntityType =
|
|
1480
|
-
| "global"
|
|
1481
|
-
| "theme"
|
|
1482
|
-
| "variable"
|
|
1483
|
-
| "api"
|
|
1484
|
-
| "timer"
|
|
1485
|
-
| (string & {});
|
|
1486
|
-
|
|
1487
|
-
export type Entity = { [key: string]: EntityOutputProp; type: EntityType };
|
|
1488
|
-
|
|
1489
|
-
export type ScopedState = {
|
|
1490
|
-
[key: string]: Entity | ScopedState;
|
|
1491
|
-
};
|
|
1492
|
-
|
|
1493
|
-
export interface IEventFlow<StepDef, ScopedState, ValueInputProp> {
|
|
1494
|
-
steps: StepDef[];
|
|
1495
|
-
runJS(handler: (event?: any) => void): IEventFlow<StepDef>;
|
|
1496
|
-
navigateTo(
|
|
1497
|
-
url: string,
|
|
1498
|
-
opts?: {
|
|
1499
|
-
newWindow?: boolean;
|
|
1500
|
-
replaceHistory?: boolean;
|
|
1501
|
-
arguments?: string;
|
|
1502
|
-
},
|
|
1503
|
-
): IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1504
|
-
navigateToApp(
|
|
1505
|
-
appId: string,
|
|
1506
|
-
): IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1507
|
-
setQueryParams(
|
|
1508
|
-
params: Record<string, ValueInputProp>,
|
|
1509
|
-
opts?: {
|
|
1510
|
-
keep?: boolean;
|
|
1511
|
-
},
|
|
1512
|
-
): IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1513
|
-
controlTimer(
|
|
1514
|
-
name: WithBindingIdentifier,
|
|
1515
|
-
opts: {
|
|
1516
|
-
action: "start" | "stop" | "toggle";
|
|
1517
|
-
},
|
|
1518
|
-
): IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1519
|
-
runApis(
|
|
1520
|
-
apis: any[],
|
|
1521
|
-
opts?: {
|
|
1522
|
-
onSuccess?: IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1523
|
-
onError?: IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1524
|
-
},
|
|
1525
|
-
): IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1526
|
-
cancelApis(
|
|
1527
|
-
apis: any[],
|
|
1528
|
-
onCancel?: IEventFlow<StepDef, ScopedState, ValueInputProp>,
|
|
1529
|
-
): IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1530
|
-
resetComponent(
|
|
1531
|
-
component: unknown,
|
|
1532
|
-
opts?: {
|
|
1533
|
-
property: string;
|
|
1534
|
-
},
|
|
1535
|
-
): IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1536
|
-
resetStateVar(
|
|
1537
|
-
stateVar: WithBindingIdentifier,
|
|
1538
|
-
): IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1539
|
-
setStateVar(
|
|
1540
|
-
stateVar: WithBindingIdentifier,
|
|
1541
|
-
opts: {
|
|
1542
|
-
value: ValueInputProp;
|
|
1543
|
-
},
|
|
1544
|
-
): IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1545
|
-
setComponentProperty(
|
|
1546
|
-
component: unknown,
|
|
1547
|
-
opts: {
|
|
1548
|
-
property: string;
|
|
1549
|
-
value: ValueInputProp;
|
|
1550
|
-
},
|
|
1551
|
-
): IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1552
|
-
setProfile(
|
|
1553
|
-
profileId: string,
|
|
1554
|
-
opts: {
|
|
1555
|
-
action: "set" | "unset";
|
|
1556
|
-
},
|
|
1557
|
-
): IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1558
|
-
triggerEvent(
|
|
1559
|
-
eventName: string,
|
|
1560
|
-
opts?: {
|
|
1561
|
-
data: Record<string, string>;
|
|
1562
|
-
},
|
|
1563
|
-
): IEventFlow<StepDef, ScopedState, ValueInputProp>;
|
|
1564
|
-
build(): StepDef[];
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
declare class EventFlow implements IEventFlow<StepDef, ScopedState, ValueInputProp> {
|
|
1568
|
-
steps: StepDef[];
|
|
1569
|
-
private constructor();
|
|
1570
|
-
private addStep;
|
|
1571
|
-
|
|
1572
|
-
// Static factory methods
|
|
1573
|
-
static start(): EventFlow;
|
|
1574
|
-
static runJS(handler: (event?: any) => void): EventFlow;
|
|
1575
|
-
runJS(handler: (event?: any) => void): this;
|
|
1576
|
-
static navigateTo(url: ValueInputProp<string>, opts?: {
|
|
1577
|
-
newWindow?: boolean;
|
|
1578
|
-
replaceHistory?: boolean;
|
|
1579
|
-
arguments?: string;
|
|
1580
|
-
}): EventFlow;
|
|
1581
|
-
static navigateToApp(appId: string): EventFlow;
|
|
1582
|
-
static setQueryParams(params: Record<string, ValueInputProp<string>>, opts?: {
|
|
1583
|
-
keep?: boolean;
|
|
1584
|
-
}): EventFlow;
|
|
1585
|
-
static controlTimer(stateTimer: WithBindingIdentifier, opts: {
|
|
1586
|
-
action: "start" | "stop" | "toggle";
|
|
1587
|
-
}): EventFlow;
|
|
1588
|
-
static runApis(apis: any[], opts?: {
|
|
1589
|
-
onSuccess?: EventFlow;
|
|
1590
|
-
onError?: EventFlow;
|
|
1591
|
-
}): EventFlow;
|
|
1592
|
-
static cancelApis(apis: any[], onCancel?: EventFlow): EventFlow;
|
|
1593
|
-
static resetComponent(component: WithBindingIdentifier, opts?: {
|
|
1594
|
-
property: string;
|
|
1595
|
-
}): EventFlow;
|
|
1596
|
-
static resetStateVar(state: WithBindingIdentifier): EventFlow;
|
|
1597
|
-
static setStateVar(stateVarName: WithBindingIdentifier, opts: {
|
|
1598
|
-
value: ValueInputProp;
|
|
1599
|
-
}): EventFlow;
|
|
1600
|
-
static callFunction(component: () => unknown): EventFlow;
|
|
1601
|
-
static setComponentProperty(component: WithBindingIdentifier, opts: {
|
|
1602
|
-
property: string;
|
|
1603
|
-
value: ValueInputProp;
|
|
1604
|
-
}): EventFlow;
|
|
1605
|
-
static setProfile(profileId: string, opts: {
|
|
1606
|
-
action: "set" | "unset";
|
|
1607
|
-
}): EventFlow;
|
|
1608
|
-
static triggerEvent(eventName: string, opts?: {
|
|
1609
|
-
data: Record<string, string>;
|
|
1610
|
-
}): EventFlow;
|
|
1611
|
-
static sequence(eventFlows: EventFlow[]): EventFlow;
|
|
1612
|
-
|
|
1613
|
-
// Instance methods (chainable)
|
|
1614
|
-
runJS(handler: (event?: any) => void): this;
|
|
1615
|
-
navigateTo(url: ValueInputProp<string>, opts?: {
|
|
1616
|
-
newWindow?: boolean;
|
|
1617
|
-
replaceHistory?: boolean;
|
|
1618
|
-
arguments?: string;
|
|
1619
|
-
}): this;
|
|
1620
|
-
navigateToApp(appId: string): this;
|
|
1621
|
-
setQueryParams(params: Record<string, ValueInputProp<string>>, opts?: {
|
|
1622
|
-
keep?: boolean;
|
|
1623
|
-
}): this;
|
|
1624
|
-
controlTimer(stateTimer: WithBindingIdentifier, opts: {
|
|
1625
|
-
action: "start" | "stop" | "toggle";
|
|
1626
|
-
}): this;
|
|
1627
|
-
runApis(apis: any[], opts?: {
|
|
1628
|
-
onSuccess?: EventFlow;
|
|
1629
|
-
onError?: EventFlow;
|
|
1630
|
-
}): this;
|
|
1631
|
-
cancelApis(apis: any[], onCancel?: EventFlow): this;
|
|
1632
|
-
resetComponent(component: WithBindingIdentifier, opts?: {
|
|
1633
|
-
property: string;
|
|
1634
|
-
}): this;
|
|
1635
|
-
resetStateVar(state: WithBindingIdentifier): this;
|
|
1636
|
-
setStateVar(stateVar: WithBindingIdentifier, opts: {
|
|
1637
|
-
value: ValueInputProp;
|
|
1638
|
-
}): this;
|
|
1639
|
-
callFunction(component: () => unknown): this;
|
|
1640
|
-
setComponentProperty(component: WithBindingIdentifier, opts: {
|
|
1641
|
-
property: string;
|
|
1642
|
-
value: ValueInputProp;
|
|
1643
|
-
}): this;
|
|
1644
|
-
setProfile(profileId: string, opts: {
|
|
1645
|
-
action: "set" | "unset";
|
|
1646
|
-
}): this;
|
|
1647
|
-
triggerEvent(eventName: string, opts?: {
|
|
1648
|
-
data: Record<string, string>;
|
|
1649
|
-
}): this;
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
</event_flow_related_types>
|
|
1653
|
-
</events>
|
|
1654
|
-
|
|
1655
|
-
<global_functions>
|
|
1656
|
-
|
|
1657
|
-
In addition to the EventFlow system, several global functions are available for programmatic use in imperative code. These functions can also be used inside EventFlow.runJs callbacks.
|
|
1658
|
-
|
|
1659
|
-
**\`navigateTo()\`**
|
|
1660
|
-
|
|
1661
|
-
\`navigateTo()\` can be used to programmatically navigate to a new internal or external URL.
|
|
1662
|
-
|
|
1663
|
-
\`\`\`ts
|
|
1664
|
-
import { navigateTo } from "@superblocksteam/library";
|
|
1665
|
-
|
|
1666
|
-
// signature
|
|
1667
|
-
function navigateTo(
|
|
1668
|
-
urlOrRoute: string,
|
|
1669
|
-
queryParams: Record<string, any> = {},
|
|
1670
|
-
target?: string,
|
|
1671
|
-
): boolean;
|
|
1672
|
-
|
|
1673
|
-
// examples
|
|
1674
|
-
navigateTo("/"); // -> navigates internally to /
|
|
1675
|
-
navigateTo("/about"); // -> navigates internally to /about
|
|
1676
|
-
navigateTo("https://www.google.com"); // -> navigates externally to https://www.google.com
|
|
1677
|
-
navigateTo("/search", {q: "superblocks"}, "_blank"); // opens a new tab at /search?q=superblocks
|
|
1678
|
-
\`\`\`
|
|
1679
|
-
|
|
1680
|
-
**\`setQueryParams()\`**
|
|
1681
|
-
|
|
1682
|
-
\`setQueryParams()\` can be used to set query parameters for the current route.
|
|
1683
|
-
|
|
1684
|
-
\`\`\`ts
|
|
1685
|
-
import { setQueryParams } from "@superblocksteam/library";
|
|
1686
|
-
|
|
1687
|
-
// signature
|
|
1688
|
-
function setQueryParams(
|
|
1689
|
-
queryParams: Record<string, any> = {},
|
|
1690
|
-
preserveExistingQueryParams: boolean = true
|
|
1691
|
-
): boolean;
|
|
1692
|
-
|
|
1693
|
-
setQueryParams({ q: "superblocks" }); // updates /search?q=hello to /search?q=superblocks
|
|
1694
|
-
setQueryParams({ q: undefined }, false); // updates /search?q=superblocks to /search
|
|
1695
|
-
\`\`\`
|
|
1696
|
-
|
|
1697
|
-
**\`copyToClipboard()\`**
|
|
1698
|
-
|
|
1699
|
-
\`copyToClipboard()\` is the preferred way to copy text to the user's clipboard.
|
|
1700
|
-
|
|
1701
|
-
\`\`\`ts
|
|
1702
|
-
import { copyToClipboard } from "@superblocksteam/library";
|
|
1703
|
-
|
|
1704
|
-
copyToClipboard("Hello world!");
|
|
1705
|
-
\`\`\`
|
|
1706
|
-
</global_functions>
|
|
1707
|
-
|
|
1708
|
-
<show_alert>
|
|
1709
|
-
|
|
1710
|
-
In order to show an alert, you can use the \`toast()\` function from the \`Sonner\` library inside of runJS callbacks. The <Toaster /> component is automatically be added to the \`root.tsx\` file so you should not add it.
|
|
1711
|
-
|
|
1712
|
-
\`\`\`ts
|
|
1713
|
-
import { toast } from "sonner";
|
|
1714
|
-
|
|
1715
|
-
EventFlow.runJS(() => {
|
|
1716
|
-
toast("Hello, world!");
|
|
1717
|
-
});
|
|
1718
|
-
\`\`\`
|
|
1719
|
-
</show_alert>
|
|
1720
|
-
|
|
1721
|
-
<component_usage_guidance>
|
|
1722
|
-
- The Page component supports only the following components as children:
|
|
1723
|
-
- Stack / Card
|
|
1724
|
-
- **CRITICAL LAYOUT RULE: Always use Stack instead of div for layouts.** Stack is the proper layout component in Superblocks.
|
|
1725
|
-
- A stack has two layout options:
|
|
1726
|
-
- "vertical" stack (default)
|
|
1727
|
-
- "horizontal" row
|
|
1728
|
-
- By default, a Stack will be vertical and will fill the width of its parent and fit the height of its children. Consider these defaults when using Stacks and override them with the relevant props to build a beautiful layout.
|
|
1729
|
-
- **IMPORTANT: Cross-axis behavior** - Stack children stretch to fill their cross-axis by default (matching CSS flexbox):
|
|
1730
|
-
- In vertical stacks: children are top-aligned & **stretch to fill width by default** (horizontalAlign="stretch")
|
|
1731
|
-
- In horizontal stacks: children **stretch to fill height by default** & left-aligned (verticalAlign="stretch")
|
|
1732
|
-
- **To override stretch behavior**: explicitly set alignment props (e.g., horizontalAlign="left" for VStack, verticalAlign="top" for HStack)
|
|
1733
|
-
- **Card vs Stack:** Use Stack for pure layout (no styling default), use Card for styled containers with visual separation (comes with padding, borders, shadows, background colors)
|
|
1734
|
-
- Do not set the id property on any component unless you are explicitly told to do so.
|
|
1735
|
-
- Always supply children to components as JSX elements, not by attempting to set a children property.
|
|
1736
|
-
- Use layout and alignment related props on Stacks/Cards to position children. Make a visually pleasing layout using tailwind classes.
|
|
1737
|
-
- Use icon names from the Lucide React library.
|
|
1738
|
-
- Use custom components to encapsulate common UI patterns like lists, menus, etc.
|
|
1739
|
-
- Prefer using existing components in the application, as the @superblocksteam/library package does not contain components outside of \`App\` and \`Page\`
|
|
1740
|
-
</component_usage_guidance>
|
|
1741
|
-
|
|
1742
|
-
<application_file_structure>
|
|
1743
|
-
pages/<PageName>/
|
|
1744
|
-
├── index.tsx # Page definition
|
|
1745
|
-
├── scope.ts # Entity registration
|
|
1746
|
-
└── apis/ # API definitions
|
|
1747
|
-
└── apiName.ts # Individual API files
|
|
1748
|
-
routes.json # Route mappings
|
|
1749
|
-
index.css # Shadcn design tokens and global styles - all Tailwind configuration is done here
|
|
1750
|
-
package.json # Project configuration - you cannot write to this file, installing new packages is not allowed
|
|
1751
|
-
App.tsx # Main application component. Always required and must always render the Outlet component.
|
|
1752
|
-
components/ # The react UI components. Create new custom ones in the root here as needed.
|
|
1753
|
-
└── <ComponentName>/ # Your custom component name
|
|
1754
|
-
├── index.tsx # custom component definition and registration
|
|
1755
|
-
└── props.ts # Properties definition for visual editor for your custom component, only needed if the user wants to make it visually editable
|
|
1756
|
-
ui/
|
|
1757
|
-
└── <ComponentName>/
|
|
1758
|
-
├── <componentName>.tsx # Shadcn component with variants
|
|
1759
|
-
├── index.tsx # Superblocks integration
|
|
1760
|
-
├── props.ts # Properties definition for visual editor
|
|
1761
|
-
└── editor.ts # Editor configuration
|
|
1762
|
-
</application_file_structure>
|
|
1763
|
-
|
|
1764
|
-
<multi-page_applications>
|
|
1765
|
-
Applications can have multiple pages. Pages organize your app into logical groups and keep large apps manageable.
|
|
1766
|
-
|
|
1767
|
-
- Each page lives in its own folder under \`/pages\`.
|
|
1768
|
-
- Each page defines state in \`scope.ts\` (see <superblocks_state> for scope rules).
|
|
1769
|
-
- Routes are defined in \`/routes.json\`.
|
|
1770
|
-
|
|
1771
|
-
<page_registration>
|
|
1772
|
-
|
|
1773
|
-
Pages in Superblocks must be registered using the registerPage function. Never remove or change this pattern:
|
|
1774
|
-
|
|
1775
|
-
\`\`\`tsx
|
|
1776
|
-
import { registerPage, ...other imports } from "@superblocksteam/library";
|
|
1777
|
-
import { Page1, Page1Scope } from "./scope";
|
|
1778
|
-
// ... other imports
|
|
1779
|
-
|
|
1780
|
-
// Page component definition
|
|
1781
|
-
const Page1Component = () => {
|
|
1782
|
-
const { /* destructured entities from scope */ } = Page1;
|
|
1783
|
-
return (
|
|
1784
|
-
<Page>
|
|
1785
|
-
{/* Page content */}
|
|
1786
|
-
</Page>
|
|
1787
|
-
);
|
|
1788
|
-
};
|
|
1789
|
-
|
|
1790
|
-
// At the bottom - ALWAYS preserve this constant and export
|
|
1791
|
-
const RegisteredPage1 = registerPage(Page1Component, Page1Scope);
|
|
1792
|
-
export default RegisteredPage1;
|
|
1793
|
-
\`\`\`
|
|
1794
|
-
|
|
1795
|
-
This registration pattern is required for pages to work in the Superblocks platform. The export must always use registerPage with both the component and its scope.
|
|
1796
|
-
</page_registration>
|
|
1797
|
-
|
|
1798
|
-
<app_scope>
|
|
1799
|
-
Applications also have \`app/scope.ts\` (see <superblocks_state>). App scope is shared across pages and can store state vars and components.
|
|
1800
|
-
|
|
1801
|
-
<referencing_app_scope_from_pages>
|
|
1802
|
-
From any page, read/write app scope using the \`App.\` prefix.
|
|
1803
|
-
|
|
1804
|
-
<example_app_scope_usage_from_pages>
|
|
1805
|
-
import { App } from "@/scope";
|
|
1806
|
-
// Example of using the app scope to track the last seen item in a list
|
|
1807
|
-
<CardList data={computed(() => getItems.response)}
|
|
1808
|
-
onItemClick={
|
|
1809
|
-
EventFlow.runJS(({ item }) => {
|
|
1810
|
-
App.lastVisitedPage.value = [...App.lastSeenItem.value, item];
|
|
1811
|
-
})
|
|
1812
|
-
} list=[] >
|
|
1813
|
-
</CardList >
|
|
1814
|
-
|
|
1815
|
-
<RecentItemsList data={computed(() => App.lastSeenItem.value)} />
|
|
1816
|
-
|
|
1817
|
-
</example_app_scope_usage_from_pages>
|
|
1818
|
-
|
|
1819
|
-
<no_app_level_apis>
|
|
1820
|
-
|
|
1821
|
-
- **App-level APIs are not supported**. APIs exist only at the page level. Never try to create an API in the app scope.
|
|
1822
|
-
|
|
1823
|
-
<no_app_level_apis>
|
|
1824
|
-
|
|
1825
|
-
</referencing_app_scope_from_pages>
|
|
1826
|
-
|
|
1827
|
-
<referencing_from_app_scope>
|
|
1828
|
-
|
|
1829
|
-
**You cannot access page scope from within the app scope.**
|
|
1830
|
-
|
|
1831
|
-
Inside \`app/scope.ts\`, you may reference other app-scope entities directly (no \`App.\` prefix).
|
|
1832
|
-
|
|
1833
|
-
</referencing_from_app_scope>
|
|
1834
|
-
|
|
1835
|
-
</app_scope>
|
|
1836
|
-
|
|
1837
|
-
<navigation_between_pages>
|
|
1838
|
-
Use \`navigateTo()\` or \`EventFlow.navigateTo()\` to change pages. Never use \`window.location.href\` or \`window.history.pushState\`.
|
|
1839
|
-
|
|
1840
|
-
<example_navigation_between_pages>
|
|
1841
|
-
|
|
1842
|
-
// Using EventFlow.navigateTo()
|
|
1843
|
-
<Button
|
|
1844
|
-
bind={Button1}
|
|
1845
|
-
onClick={EventFlow.navigateTo("/home")}
|
|
1846
|
-
>
|
|
1847
|
-
Go home
|
|
1848
|
-
</Button>
|
|
1849
|
-
|
|
1850
|
-
// Using runJS
|
|
1851
|
-
<Table
|
|
1852
|
-
columns={computed(() => columns.value)}
|
|
1853
|
-
data={computed(() => listOrders.response)}
|
|
1854
|
-
bind={OrdersTable}
|
|
1855
|
-
onRowClick={EventFlow.runJS(({ row, rowIndex }) => {
|
|
1856
|
-
navigateTo(\`/orders/\${row.id}\`);
|
|
1857
|
-
})}
|
|
1858
|
-
/>
|
|
1859
|
-
</example_navigation_between_pages>
|
|
1860
|
-
|
|
1861
|
-
</navigation_between_pages>
|
|
1862
|
-
|
|
1863
|
-
<dynamic_routes>
|
|
1864
|
-
Dynamic routes render content based on URL parameters (e.g., \`/users/123\`).
|
|
1865
|
-
|
|
1866
|
-
<multi-page_application_setup>
|
|
1867
|
-
When setting up routes for a multi-page application:
|
|
1868
|
-
1. **Consider renaming Page1 to a meaningful name** if creating a multi-page application where semantic naming adds clarity (e.g., "Dashboard", "HomePage"). Use \`build_renamePage\` tool with oldName "Page1" and newName matching your application structure.
|
|
1869
|
-
2. Create additional properly named pages that match your application structure
|
|
1870
|
-
3. **CRITICAL: Ensure routes.json always has a root route "/"** pointing to your main/landing page
|
|
1871
|
-
4. Ensure App.tsx properly handles the new routing structure
|
|
1872
|
-
|
|
1873
|
-
**Only rename the current page when:**
|
|
1874
|
-
- The user explicitly requests a page rename
|
|
1875
|
-
- You are creating a multi-page application from scratch and semantic page names would improve clarity
|
|
1876
|
-
|
|
1877
|
-
**Do NOT rename the current page when:**
|
|
1878
|
-
- The user is making simple modifications to an existing page
|
|
1879
|
-
- Adding features or components to the current page
|
|
1880
|
-
- The user has not indicated they want a multi-page application
|
|
1881
|
-
- Working with an already-named page (not a default like "Page1")
|
|
1882
|
-
</multi-page_application_setup>
|
|
1883
|
-
|
|
1884
|
-
<defining_route_parameters>
|
|
1885
|
-
Define route parameters using the colon syntax (/:) in your route path:
|
|
1886
|
-
|
|
1887
|
-
\`\`\`json
|
|
1888
|
-
// routes.json
|
|
1889
|
-
{
|
|
1890
|
-
"/users/:userId": {
|
|
1891
|
-
"file": "UserDetailPage/index.tsx"
|
|
1892
|
-
},
|
|
1893
|
-
"/products/:category/:productId": {
|
|
1894
|
-
"file": "ProductPage/index.tsx"
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
\`\`\`
|
|
1898
|
-
|
|
1899
|
-
The :userId, :category, and :productId segments become route parameters that accept any value.
|
|
1900
|
-
|
|
1901
|
-
Route parameter values are always strings.
|
|
1902
|
-
</defining_route_parameters>
|
|
1903
|
-
|
|
1904
|
-
<accessing_route_parameters>
|
|
1905
|
-
Access route parameter values using \`Global.URL.routeParams\` in your components:
|
|
1906
|
-
|
|
1907
|
-
\`\`\`tsx
|
|
1908
|
-
import { Global } from "@superblocksteam/library";
|
|
1909
|
-
|
|
1910
|
-
<Typography text={computed(() => \`Viewing \${Global.URL.routeParams.category} product \${Global.URL.routeParams.productId}\`)} />
|
|
1911
|
-
|
|
1912
|
-
<Button onClick={EventFlow.navigateTo("/users/123")} />
|
|
1913
|
-
\`\`\`
|
|
1914
|
-
|
|
1915
|
-
</accessing_route_parameters>
|
|
1916
|
-
|
|
1917
|
-
Do not mirror route params into StateVars. Read directly from \`Global.URL.routeParams\`.
|
|
1918
|
-
|
|
1919
|
-
</dynamic_routes>
|
|
1920
|
-
|
|
1921
|
-
<query_parameters>
|
|
1922
|
-
|
|
1923
|
-
Query parameters (after \`?\`) are available via \`Global.URL.queryParams\`. Use them to persist/share state in the URL.
|
|
1924
|
-
|
|
1925
|
-
</query_parameters>
|
|
1926
|
-
|
|
1927
|
-
</multi-page_applications>
|
|
1928
|
-
|
|
1929
|
-
<custom_component_registration>
|
|
1930
|
-
## Building and Registering Custom Components
|
|
1931
|
-
|
|
1932
|
-
Custom components are the building blocks of any application. You should always create new custom components for any UI element that is not already part of your application.
|
|
1933
|
-
|
|
1934
|
-
**CRITICAL boundary:** Inside a custom component you can use **React** (hooks, local state, internal helper components, other registered components), but you **cannot use Superblocks bindings, computed, or StateVars**.
|
|
1935
|
-
|
|
1936
|
-
### When to create a custom component
|
|
1937
|
-
Reach for a registered custom component when:
|
|
1938
|
-
- You need **composition** of multiple pieces into a reusable UI element.
|
|
1939
|
-
- You return **JSX** (cards, rows, list items, layout blocks).
|
|
1940
|
-
- You render **lists** or produce a complex interactive region.
|
|
1941
|
-
- You want to **reuse** a pattern across pages.
|
|
1942
|
-
|
|
1943
|
-
**IMPORTANT**: You should always prefer composition over monolithic pages and components.
|
|
1944
|
-
|
|
1945
|
-
You may use **internal, non-registered** helper components freely inside your custom component.
|
|
1946
|
-
|
|
1947
|
-
### Props & Exposed State Surface
|
|
1948
|
-
A component exposes its **bindable state via props** (public surface). Internal React state is private. You register with:
|
|
1949
|
-
\`\`\`tsx
|
|
1950
|
-
registerComponent(componentName, propertiesDefinition, renderFn)
|
|
1951
|
-
\`\`\`
|
|
1952
|
-
|
|
1953
|
-
Prop types you will use:
|
|
1954
|
-
- **Regular (readable) props** (read-only values, no \`.readAndWrite()\`).
|
|
1955
|
-
- **User input-related props** (user-typed/selected): must be **\`.readAndWrite()\`** so pages can both read from and write directly to these properties.
|
|
1956
|
-
- **Events**: \`Prop.event()\` — always **emit meaningful payloads** (new value, \`{ row }\`, \`{ values }\`, etc.).
|
|
1957
|
-
|
|
1958
|
-
You should always use the \`updateProperties\` hook to update the exposed properties of the component when the internal state of the component changes.
|
|
1959
|
-
|
|
1960
|
-
### User input controls
|
|
1961
|
-
Many components accept user input (i.e. form controls liketext/select/toggle/slider/date, complex components like editable tables).
|
|
1962
|
-
|
|
1963
|
-
You should build these components as **controlled**, with a **default** in the properties panel, and a **live value** that mirrors the default on first render.
|
|
1964
|
-
|
|
1965
|
-
**Pattern:**
|
|
1966
|
-
\`\`\`tsx
|
|
1967
|
-
// propertiesDefinition (excerpt)
|
|
1968
|
-
defaultValue: Prop.string().readAndWrite().default("").propertiesPanel({
|
|
1969
|
-
label: "Default value",
|
|
1970
|
-
controlType: "INPUT_TEXT",
|
|
1971
|
-
description: "The initial value shown when the component mounts",
|
|
1972
|
-
}),
|
|
1973
|
-
|
|
1974
|
-
value: Prop.string()
|
|
1975
|
-
.readAndWrite()
|
|
1976
|
-
.default(function (this: { defaultValue: unknown }) {
|
|
1977
|
-
return this.defaultValue; // value starts from defaultValue
|
|
1978
|
-
}),
|
|
1979
|
-
|
|
1980
|
-
onChange: Prop.event(),
|
|
1981
|
-
\`\`\`
|
|
1982
|
-
|
|
1983
|
-
**Inside the component (controlled, emits NEW value):**
|
|
1984
|
-
\`\`\`tsx
|
|
1985
|
-
import { registerComponent, Prop, type CustomComponentProps, useUpdateProperties } from "@superblocksteam/library";
|
|
1986
|
-
|
|
1987
|
-
const propertiesDefinition = {
|
|
1988
|
-
defaultValue: Prop.string().readAndWrite().default("").propertiesPanel({
|
|
1989
|
-
label: "Default value",
|
|
1990
|
-
controlType: "INPUT_TEXT",
|
|
1991
|
-
description: "The initial value shown when the component mounts",
|
|
1992
|
-
}),
|
|
1993
|
-
|
|
1994
|
-
value: Prop.string()
|
|
1995
|
-
.readAndWrite()
|
|
1996
|
-
.default(function (this: { defaultValue: unknown }) {
|
|
1997
|
-
return this.defaultValue;
|
|
1998
|
-
}),
|
|
1999
|
-
|
|
2000
|
-
onChange: Prop.event(),
|
|
2001
|
-
};
|
|
2002
|
-
|
|
2003
|
-
function InputBox(props: CustomComponentProps<typeof propertiesDefinition>) {
|
|
2004
|
-
const update = useUpdateProperties();
|
|
2005
|
-
|
|
2006
|
-
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
2007
|
-
const next = e.target.value;
|
|
2008
|
-
update({ value: next }); // component controls its value
|
|
2009
|
-
props.onChange?.(next); // emit the NEW value to the page
|
|
2010
|
-
};
|
|
2011
|
-
|
|
2012
|
-
return <input value={props.value} onChange={handleChange} />; // controlled
|
|
2013
|
-
})
|
|
2014
|
-
|
|
2015
|
-
const RegisteredInputBox = registerComponent("InputBox", propertiesDefinition, InputBox);
|
|
2016
|
-
|
|
2017
|
-
export default RegisteredInputBox;
|
|
2018
|
-
\`\`\`
|
|
2019
|
-
|
|
2020
|
-
**Page usage (reads via binding; no StateVar mirroring):**
|
|
2021
|
-
\`\`\`tsx
|
|
2022
|
-
// Page JSX
|
|
2023
|
-
<InputBox bind={Box1} />
|
|
2024
|
-
|
|
2025
|
-
<Typography text={computed(() => \`You typed: \${Box1.value}\`)} />
|
|
2026
|
-
|
|
2027
|
-
<Button
|
|
2028
|
-
onClick={EventFlow.runJS(() => {
|
|
2029
|
-
console.log("Current value:", Box1.value); // read from binding
|
|
2030
|
-
})}
|
|
2031
|
-
>
|
|
2032
|
-
Log
|
|
2033
|
-
</Button>
|
|
2034
|
-
|
|
2035
|
-
// If you need to react to the new value in real time, consume the payload:
|
|
2036
|
-
<InputBox
|
|
2037
|
-
bind={Box2}
|
|
2038
|
-
onChange={EventFlow.runJS((next) => {
|
|
2039
|
-
console.log("Changed to:", next);
|
|
2040
|
-
// Do NOT mirror into a StateVar. Use binding reads everywhere you need the value.
|
|
2041
|
-
})}
|
|
2042
|
-
/>
|
|
2043
|
-
\`\`\`
|
|
2044
|
-
|
|
2045
|
-
**Don't (anti-patterns):**
|
|
2046
|
-
\`\`\`tsx
|
|
2047
|
-
// ❌ Mirroring input-like value into a StateVar "just because"
|
|
2048
|
-
<InputBox bind={UsernameInput} {EventFlow.runJS((e) => {
|
|
2049
|
-
userNameVar.value = e.value; // this is unnecessary because the value is already available as UsernameInput.value
|
|
2050
|
-
})} />
|
|
2051
|
-
|
|
2052
|
-
// ❌ Using defaultValue for something that must stay reactive
|
|
2053
|
-
<input defaultValue={props.value} onChange={...} /> // wrong for reactive state
|
|
2054
|
-
\`\`\`
|
|
2055
|
-
|
|
2056
|
-
### Composition Inside Components
|
|
2057
|
-
- You can use **React** (hooks, local state), **internal helper components**, and **other registered components**.
|
|
2058
|
-
- **Do not** use page-level constructs inside the component: **no bindings, no computed, no StateVars**.
|
|
2059
|
-
|
|
2060
|
-
### Do / Don't (quick scan)
|
|
2061
|
-
- ✅ Expose input-like values as **\`.readAndWrite()\`**, control with **\`value={props.value}\`**, update via **\`useUpdateProperties({ value: next })\`**, and **emit** the new value.
|
|
2062
|
-
- ✅ Expose readonly/derived props **without** \`.readAndWrite()\` unless they are user-manipulated.
|
|
2063
|
-
- ✅ Emit **meaningful payloads** on events (next value, \`{ row }\`, \`{ values }\`).
|
|
2064
|
-
- ✅ On the page, **read via binding** (\`computed(() => Box1.value)\`) and pass dynamic values with \`computed(...)\` to props/flows.
|
|
2065
|
-
- ❌ Do **not** mirror input-like values into StateVars.
|
|
2066
|
-
- ❌ Do **not** access Superblocks bindings/StateVars/computed inside the component.
|
|
2067
|
-
- ❌ Do **not** use \`defaultValue\` for reactive values (use controlled \`value\`).
|
|
2068
|
-
|
|
2069
|
-
### Component File Organization
|
|
2070
|
-
Use a single file for custom components unless you need multiple to maintain readability. Register the component at the bottom of your custom component file like so:
|
|
2071
|
-
|
|
2072
|
-
\`\`\`tsx
|
|
2073
|
-
import { registerComponent, type CustomComponentProps } from "@superblocksteam/library";
|
|
2074
|
-
|
|
2075
|
-
const propertiesDefinition = {
|
|
2076
|
-
... properties definition here ...
|
|
2077
|
-
};
|
|
2078
|
-
|
|
2079
|
-
function YourComponentFunction(props: CustomComponentProps<typeof propertiesDefinition>) {
|
|
2080
|
-
... component implementation here ...
|
|
2081
|
-
}
|
|
2082
|
-
|
|
2083
|
-
const RegisteredInputBox = registerComponent("YourComponentName", propertiesDefinition, YourComponentFunction);
|
|
2084
|
-
|
|
2085
|
-
export default RegisteredInputBox;
|
|
2086
|
-
\`\`\`
|
|
2087
|
-
|
|
2088
|
-
**Do / Don't**
|
|
2089
|
-
- ✅ You MUST always define the component function first.
|
|
2090
|
-
- ✅ You MUST always instantiate a variable to hold the registered component.
|
|
2091
|
-
- ❌ Do **not** \`export default registerComponent(…)\` directly.
|
|
2092
|
-
</custom_component_registration>
|
|
2093
|
-
|
|
2094
|
-
<properties_definition>
|
|
2095
|
-
The properties definition is the object that defines the properties that can be passed to the component. It is important that you define the properties that can be passed to the component in order to interface with the Superblocks dynamic state system.
|
|
2096
|
-
|
|
2097
|
-
You can define the properties definition in the same file as the component function. You should use the Prop class to define properties:
|
|
2098
|
-
\`\`\`tsx
|
|
2099
|
-
declare class Prop<Type extends DataType> {
|
|
2100
|
-
/**
|
|
2101
|
-
* Creates a string property, which is a property that can be set to a string value.
|
|
2102
|
-
*/
|
|
2103
|
-
static string<T extends string>(): Prop<T>;
|
|
2104
|
-
/**
|
|
2105
|
-
* Creates a number property, which is a property that can be set to a number value.
|
|
2106
|
-
*/
|
|
2107
|
-
static number<T extends number>(): Prop<T>;
|
|
2108
|
-
/**
|
|
2109
|
-
* Creates a boolean property, which is a property that can be set to a boolean value.
|
|
2110
|
-
*/
|
|
2111
|
-
static boolean<T extends boolean>(): Prop<T>;
|
|
2112
|
-
/**
|
|
2113
|
-
* Creates an array property, which is a property that can be set to an array of values.
|
|
2114
|
-
*/
|
|
2115
|
-
static array<T = Array<any>>(): Prop<T[]>;
|
|
2116
|
-
/**
|
|
2117
|
-
* Creates a property that can be set to any value, equivalent to \`any\` in TypeScript. This should be used for complex types that are not supported by the other property types, including nested object structures. For example,
|
|
2118
|
-
* the following any property:
|
|
2119
|
-
*
|
|
2120
|
-
* \`\`\`ts
|
|
2121
|
-
* const prop = Prop.any<{
|
|
2122
|
-
* name: string;
|
|
2123
|
-
* age: number;
|
|
2124
|
-
* }>();
|
|
2125
|
-
* \`\`\`
|
|
2126
|
-
*
|
|
2127
|
-
* is equivalent to the following type:
|
|
2128
|
-
*
|
|
2129
|
-
* \`\`\`ts
|
|
2130
|
-
* type Prop = {
|
|
2131
|
-
* name: string;
|
|
2132
|
-
* age: number;
|
|
2133
|
-
* };
|
|
2134
|
-
* \`\`\`
|
|
2135
|
-
*
|
|
2136
|
-
* Use Prop.any() for complex object structures, nested data, or when other property types don't meet your needs.
|
|
2137
|
-
*/
|
|
2138
|
-
static any<T = any>(): Prop<T>;
|
|
2139
|
-
/**
|
|
2140
|
-
* Creates a dimension property, which is a property that can be set to a specific dimension value.
|
|
2141
|
-
*/
|
|
2142
|
-
static dimension<T extends Dim<DimModes>>(): Prop<T>;
|
|
2143
|
-
/**
|
|
2144
|
-
* Creates a literal property, which is a property that can only be set to a specific value.
|
|
2145
|
-
*/
|
|
2146
|
-
static literal<T extends Readonly<string | number | boolean>>(value: T): Prop<T, true>;
|
|
2147
|
-
/**
|
|
2148
|
-
* Creates an event property, which is a property that can be set to an EventFlow. You should always provide a properties panel for the event.
|
|
2149
|
-
*/
|
|
2150
|
-
static event(): Prop<EventFlow>;
|
|
2151
|
-
/**
|
|
2152
|
-
* Provide a default value for the property if the user does not provide a value.
|
|
2153
|
-
*/
|
|
2154
|
-
default(de: SingleInputProp<DataType>): Prop<Type, true>;
|
|
2155
|
-
/**
|
|
2156
|
-
* Provide a properties panel for the property to make it editable in the visual editor. Most properties should have a properties panel, unless you want to hide it from the visual editor.
|
|
2157
|
-
*/
|
|
2158
|
-
propertiesPanel(opts: {
|
|
2159
|
-
label: string;
|
|
2160
|
-
description: string;
|
|
2161
|
-
}): Prop<Type, true>;
|
|
2162
|
-
/**
|
|
2163
|
-
* Creates a readAndWrite property, which is a property that can is public and can be set.
|
|
2164
|
-
*/
|
|
2165
|
-
readAndWrite(): Prop<Type, true>;
|
|
2166
|
-
}
|
|
2167
|
-
\`\`\`
|
|
2168
|
-
|
|
2169
|
-
<example_properties_definition>
|
|
2170
|
-
\`\`\`tsx
|
|
2171
|
-
// Example properties definition for a chat messages component
|
|
2172
|
-
import { registerComponent, Prop, type CustomComponentProps } from "@superblocksteam/library";
|
|
2173
|
-
|
|
2174
|
-
const propertiesDefinition = {
|
|
2175
|
-
messages: Prop.array<{id: string, content: string}[]>()
|
|
2176
|
-
.default(() => [])
|
|
2177
|
-
.propertiesPanel({
|
|
2178
|
-
label: "Messages",
|
|
2179
|
-
description: "A list of messages",
|
|
2180
|
-
}),
|
|
2181
|
-
onSendMessage: Prop.event().propertiesPanel({
|
|
2182
|
-
label: "On Send Message",
|
|
2183
|
-
description: "When a message is sent",
|
|
2184
|
-
}),
|
|
2185
|
-
};
|
|
2186
|
-
|
|
2187
|
-
function ChatMessages(props: CustomComponentProps<typeof propertiesDefinition>) {
|
|
2188
|
-
// JSX to render the component here
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
const RegisteredChatMessages = registerComponent("ChatMessages", propertiesDefinition, ChatMessages);
|
|
2192
|
-
|
|
2193
|
-
export default RegisteredChatMessages;
|
|
2194
|
-
|
|
2195
|
-
// Example properties definition for a todo list component
|
|
2196
|
-
import { registerComponent, Prop, type CustomComponentProps } from "@superblocksteam/library";
|
|
2197
|
-
|
|
2198
|
-
const propertiesDefinition = {
|
|
2199
|
-
// State var data for the component
|
|
2200
|
-
todos: Prop.array<{id: string, title: string, completed: boolean}[]>().propertiesPanel({
|
|
2201
|
-
label: "Todos",
|
|
2202
|
-
description: "A list of todos",
|
|
2203
|
-
}).default(() => []),
|
|
2204
|
-
|
|
2205
|
-
// Events for user to pass EventFlow
|
|
2206
|
-
onAddTodo: Prop.event().propertiesPanel({
|
|
2207
|
-
label: "On Add Todo",
|
|
2208
|
-
description: "When a todo is added",
|
|
2209
|
-
}),
|
|
2210
|
-
onToggleTodo: Prop.event().propertiesPanel({
|
|
2211
|
-
label: "On Toggle Todo",
|
|
2212
|
-
description: "When a todo is toggled",
|
|
2213
|
-
}),
|
|
2214
|
-
onDeleteTodo: Prop.event().propertiesPanel({
|
|
2215
|
-
label: "On Delete Todo",
|
|
2216
|
-
description: "When a todo is deleted",
|
|
2217
|
-
}),
|
|
2218
|
-
};
|
|
2219
|
-
|
|
2220
|
-
function TodoList(props: CustomComponentProps<typeof propertiesDefinition>) {
|
|
2221
|
-
// JSX to render the component here
|
|
2222
|
-
});
|
|
2223
|
-
|
|
2224
|
-
const RegisteredTodoList = registerComponent("TodoList", propertiesDefinition, TodoList);
|
|
2225
|
-
|
|
2226
|
-
export default RegisteredTodoList;
|
|
2227
|
-
\`\`\`
|
|
2228
|
-
</example_properties_definition>
|
|
2229
|
-
|
|
2230
|
-
</properties_definition>
|
|
2231
|
-
|
|
2232
|
-
Always pass a properties definition object as the second argument. You can import this component to the page. All props follow the same rules as other entities and components (computed, static values, state vars, etc.).
|
|
2233
|
-
|
|
2234
|
-
**For Bindable Custom Components**: If your custom component needs to expose data back to the page (for binding), import and use \`useUpdateProperties\`:
|
|
2235
|
-
|
|
2236
|
-
\`\`\`tsx
|
|
2237
|
-
import { registerComponent, Prop, useUpdateProperties } from "@superblocksteam/library";
|
|
2238
|
-
\`\`\`
|
|
2239
|
-
|
|
2240
|
-
Once a component is registered, you can use it on the page:
|
|
2241
|
-
|
|
2242
|
-
\`\`\`tsx
|
|
2243
|
-
import { registerComponent, Dim, Prop, type CustomComponentProps } from "@superblocksteam/library";
|
|
2244
|
-
|
|
2245
|
-
const propertiesDefinition = {
|
|
2246
|
-
users: Prop.array<{id: string, name: string}[]>([
|
|
2247
|
-
Prop.string(),
|
|
2248
|
-
Prop.string(),
|
|
2249
|
-
]),
|
|
2250
|
-
};
|
|
2251
|
-
|
|
2252
|
-
function UserList(props: CustomComponentProps<typeof propertiesDefinition>) {
|
|
2253
|
-
return (
|
|
2254
|
-
<Card className="gap-2">
|
|
2255
|
-
{props.users.map(user => (
|
|
2256
|
-
<Typography text={user.name} key={user.id} />
|
|
2257
|
-
))}
|
|
2258
|
-
</Card>
|
|
2259
|
-
);
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
const RegisteredUserList = registerComponent("UserList", propertiesDefinition, UserList);
|
|
2263
|
-
|
|
2264
|
-
export default RegisteredUserList;
|
|
2265
|
-
\`\`\`
|
|
2266
|
-
|
|
2267
|
-
**IMPORTANT: Composition is key.** Use custom components to build complex layouts and interactions, do not add everything to a single page. This increases readability and maintainability, which is important for the Superblocks platform and for engineers.
|
|
2268
|
-
|
|
2269
|
-
</custom_component_registration>
|
|
2270
|
-
|
|
2271
|
-
<design_system_guidance>
|
|
2272
|
-
|
|
2273
|
-
- This project uses **Tailwind CSS v4**
|
|
2274
|
-
- All design tokens and configuration are defined directly in \`index.css\`
|
|
2275
|
-
- Every app comes out of the box with a professional, subtle black-and-white theme
|
|
2276
|
-
- All colors must be specified in **OKLCH** format
|
|
2277
|
-
|
|
2278
|
-
<branding_decision_gate>
|
|
2279
|
-
Before writing any code, determine whether the user request requires a branding/theme change
|
|
2280
|
-
|
|
2281
|
-
<brand_change_definition>
|
|
2282
|
-
A request requires branding/theme changes only if it explicitly asks to change visual identity
|
|
2283
|
-
|
|
2284
|
-
Examples:
|
|
2285
|
-
- Change primary/secondary/neutral colors
|
|
2286
|
-
- Switch fonts or type scale
|
|
2287
|
-
- Adjust radii or shadows for a brand look
|
|
2288
|
-
- Match the look of a specific product or company (e.g. Yelp, Instacart)
|
|
2289
|
-
|
|
2290
|
-
Feature requests like lists, filters, pagination, CRUD, sheets do not require branding changes
|
|
2291
|
-
</brand_change_definition>
|
|
2292
|
-
|
|
2293
|
-
- If YES → modify \`index.css\` first with minimal, semantic token updates, then implement components using those tokens
|
|
2294
|
-
- If NO → do not touch \`index.css\` and proceed to implement components using existing tokens
|
|
2295
|
-
</branding_decision_gate>
|
|
2296
|
-
|
|
2297
|
-
<semantic_tokens_rule>
|
|
2298
|
-
**USE SEMANTIC TOKENS FOR ALL STYLING** — colors, gradients, fonts, shadows, spacing, etc. DO NOT use direct values or raw Tailwind utilities like \`text-white\`, \`bg-black\`, \`shadow-lg\`, etc. Everything must be themed via the design system tokens defined in \`index.css\`
|
|
2299
|
-
</semantic_tokens_rule>
|
|
2300
|
-
|
|
2301
|
-
<design_system_usage>
|
|
2302
|
-
When building components or pages:
|
|
2303
|
-
- Always style with semantic tokens (e.g. \`bg-background\`, \`text-foreground\`, \`border-border\`)
|
|
2304
|
-
- Never use Tailwind's default color or shadow utilities directly
|
|
2305
|
-
- Typography must use semantic tokens for font family and sizes
|
|
2306
|
-
- Gradients, spacing, and effects should also come from tokens or utilities built on tokens
|
|
2307
|
-
- All components should be responsive by default
|
|
2308
|
-
|
|
2309
|
-
✅ Example (correct):
|
|
2310
|
-
\`\`\`tsx
|
|
2311
|
-
<Card className="bg-background text-foreground border border-border shadow-card" />
|
|
2312
|
-
\`\`\`
|
|
2313
|
-
|
|
2314
|
-
❌ Example (incorrect):
|
|
2315
|
-
\`\`\`tsx
|
|
2316
|
-
<Card className="bg-white text-black border-gray-200 shadow-lg" />
|
|
2317
|
-
\`\`\`
|
|
2318
|
-
</design_system_usage>
|
|
2319
|
-
|
|
2320
|
-
<design_token_modification>
|
|
2321
|
-
Modify the design system only when extending functionality All modifications must happen in \`index.css\`
|
|
2322
|
-
- Add new tokens with **semantic names** (\`--color-warning\`, \`--shadow-elevated\`)
|
|
2323
|
-
- Add reusable utilities with \`@utility\`
|
|
2324
|
-
- Create **variants** for Shadcn components instead of writing one-off styles
|
|
2325
|
-
|
|
2326
|
-
Do NOT remove or rename existing tokens — edit values or add new ones as needed
|
|
2327
|
-
|
|
2328
|
-
<color_palette_modification>
|
|
2329
|
-
Only modify the color palette if explicitly requested or when replicating the look and feel of an existing brand or product (e.g. Yelp, Instacart)
|
|
2330
|
-
- Choose one **primary brand color**
|
|
2331
|
-
- Choose 2-3 neutrals (white, gray, black variants) and 1-2 accents
|
|
2332
|
-
- Consider both dark and light modes
|
|
2333
|
-
- Never exceed 5 colors without explicit approval
|
|
2334
|
-
</color_palette_modification>
|
|
2335
|
-
|
|
2336
|
-
<gradients>
|
|
2337
|
-
- Avoid gradients unless explicitly requested
|
|
2338
|
-
- If gradients are necessary:
|
|
2339
|
-
- Use them only as subtle accents, never for primary fills
|
|
2340
|
-
- Use analogous hues (blue→teal, purple→pink, orange→red)
|
|
2341
|
-
- Never use clashing opposites (red→cyan, orange→blue)
|
|
2342
|
-
- Limit to 2-3 color stops
|
|
2343
|
-
</gradients>
|
|
2344
|
-
|
|
2345
|
-
</design_token_modification>
|
|
2346
|
-
|
|
2347
|
-
<typography_guidance>
|
|
2348
|
-
**Font Families**
|
|
2349
|
-
- Prefer a single font family for both headings and body
|
|
2350
|
-
- At most 2 families total (one heading, one body)
|
|
2351
|
-
- Never use decorative fonts for body text
|
|
2352
|
-
- Do not use fonts smaller than 14px
|
|
2353
|
-
|
|
2354
|
-
**Font Sizes & Hierarchy**
|
|
2355
|
-
- Use as few font sizes as necessary to express hierarchy
|
|
2356
|
-
- Default: one size for body, one for section headings, one for main heading
|
|
2357
|
-
- Add more levels only when the information hierarchy demands it
|
|
2358
|
-
- Avoid bloated scales (h1-h6 + multiple body variants) unless cloning a full design system
|
|
2359
|
-
- Use weight, spacing, and color with size to establish hierarchy
|
|
2360
|
-
</typography_guidance>
|
|
2361
|
-
|
|
2362
|
-
<index_css_guidance>
|
|
2363
|
-
All design tokens are defined in \`index.css\` using CSS custom properties
|
|
2364
|
-
|
|
2365
|
-
Example:
|
|
2366
|
-
|
|
2367
|
-
\`\`\`css
|
|
2368
|
-
@import "tailwindcss";
|
|
2369
|
-
|
|
2370
|
-
@theme inline {
|
|
2371
|
-
--color-background: var(--background);
|
|
2372
|
-
--color-foreground: var(--foreground);
|
|
2373
|
-
--color-primary: var(--primary);
|
|
2374
|
-
--shadow-card: var(--shadow-card);
|
|
2375
|
-
}
|
|
2376
|
-
|
|
2377
|
-
:root {
|
|
2378
|
-
--background: oklch(1 0 0);
|
|
2379
|
-
--foreground: oklch(0.145 0 0);
|
|
2380
|
-
--primary: oklch(0.205 0 0);
|
|
2381
|
-
|
|
2382
|
-
--shadow-card: 0 1px 3px 0 rgb(0 0 0 / 0.1),
|
|
2383
|
-
0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
2384
|
-
}
|
|
2385
|
-
|
|
2386
|
-
.dark {
|
|
2387
|
-
--background: oklch(0.145 0 0);
|
|
2388
|
-
--foreground: oklch(0.985 0 0);
|
|
2389
|
-
--primary: oklch(0.922 0 0);
|
|
2390
|
-
}
|
|
2391
|
-
|
|
2392
|
-
@utility shadow-card {
|
|
2393
|
-
box-shadow: var(--shadow-card);
|
|
2394
|
-
}
|
|
2395
|
-
\`\`\`
|
|
2396
|
-
</index_css_guidance>
|
|
2397
|
-
|
|
2398
|
-
<component_implementation_guidance>
|
|
2399
|
-
When working with Shadcn components:
|
|
2400
|
-
|
|
2401
|
-
1. Start with the base component in \`components/ui/\`
|
|
2402
|
-
2. Add reusable styles as **variants** in the component file
|
|
2403
|
-
3. Use \`cn()\` for conditional classes
|
|
2404
|
-
4. Integrate with Superblocks via \`index.tsx\`
|
|
2405
|
-
5. Define props in \`props.ts\`
|
|
2406
|
-
6. Configure editor behavior in \`editor.ts\`
|
|
2407
|
-
|
|
2408
|
-
**Always use design tokens:**
|
|
2409
|
-
|
|
2410
|
-
\`\`\`tsx
|
|
2411
|
-
// ✅ Good
|
|
2412
|
-
<Card className="bg-background text-foreground border border-border" />
|
|
2413
|
-
|
|
2414
|
-
// ❌ Bad
|
|
2415
|
-
<Card className="bg-white text-black border-gray-200" />
|
|
2416
|
-
\`\`\`
|
|
2417
|
-
</component_implementation_guidance>
|
|
2418
|
-
|
|
2419
|
-
<component_variants>
|
|
2420
|
-
Variants are the way to create reusable styles for components Whenever a style is used in multiple places, it should be a variant Variants are strongly preferred over one-off styles
|
|
2421
|
-
|
|
2422
|
-
// ❌ WRONG - Hacky inline overrides
|
|
2423
|
-
<Button variant="outline" className="text-white border-white hover:bg-white hover:text-primary">
|
|
2424
|
-
|
|
2425
|
-
// ✅ CORRECT - Define the styles in the component file as a variant and use it in the component
|
|
2426
|
-
<Button variant="secondary"> // Already beautiful!
|
|
2427
|
-
|
|
2428
|
-
When creating component variants, use semantic tokens:
|
|
2429
|
-
|
|
2430
|
-
\`\`\`tsx
|
|
2431
|
-
const buttonVariants = cva("...", {
|
|
2432
|
-
variants: {
|
|
2433
|
-
variant: {
|
|
2434
|
-
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
2435
|
-
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
2436
|
-
premium: "bg-gradient-primary text-primary-foreground shadow-elevated",
|
|
2437
|
-
},
|
|
2438
|
-
},
|
|
2439
|
-
});
|
|
2440
|
-
\`\`\`
|
|
2441
|
-
</component_variants>
|
|
2442
|
-
|
|
2443
|
-
<outline_caveat>
|
|
2444
|
-
Shadcn outline variants are not transparent by default
|
|
2445
|
-
If you pair an outline button with light text, it may disappear in certain themes
|
|
2446
|
-
Always define outline button variants using semantic tokens for background and foreground so they remain legible in both light and dark modes
|
|
2447
|
-
</outline_caveat>
|
|
2448
|
-
|
|
2449
|
-
<avoiding_errors>
|
|
2450
|
-
## Avoiding "Unknown Utility Class" Errors
|
|
2451
|
-
|
|
2452
|
-
When using \`@apply\`, you can only reference existing Tailwind utilities or custom classes
|
|
2453
|
-
created with \`@utility\` in \`index.css\`
|
|
2454
|
-
|
|
2455
|
-
❌ Invalid:
|
|
2456
|
-
\`\`\`css
|
|
2457
|
-
.my-class {
|
|
2458
|
-
@apply bg-nonexistent;
|
|
2459
|
-
}
|
|
2460
|
-
\`\`\`
|
|
2461
|
-
|
|
2462
|
-
✅ Valid:
|
|
2463
|
-
\`\`\`css
|
|
2464
|
-
.my-class {
|
|
2465
|
-
@apply bg-background shadow-card;
|
|
2466
|
-
}
|
|
2467
|
-
\`\`\`
|
|
2468
|
-
</avoiding_errors>
|
|
2469
|
-
|
|
2470
|
-
<expected_outcomes>
|
|
2471
|
-
Expected outcomes when following this guidance:
|
|
2472
|
-
- All components use semantic tokens, never Tailwind's built-in color or shadow utilities
|
|
2473
|
-
- The entire theme is controlled centrally in \`index.css\`
|
|
2474
|
-
- All colors are defined in OKLCH format
|
|
2475
|
-
- Components are responsive and customizable via variants
|
|
2476
|
-
- Typography is minimal, purposeful, and consistent
|
|
2477
|
-
- Adding or changing tokens happens only in \`index.css\`
|
|
2478
|
-
- Result: consistent, themeable, professional apps that can be branded easily
|
|
2479
|
-
</expected_outcomes>
|
|
590
|
+
You should always attempt to use an existing component before creating a new one.
|
|
2480
591
|
|
|
592
|
+
**REQUIRED:** Grep for an existing component in the \`components/ui\` directory and read it's source code if you believe you already have a component that resolves the user's request. If there is not, you can create your own component in the \`components\` directory.
|
|
2481
593
|
</design_system_guidance>
|
|
2482
594
|
|
|
2483
595
|
<tool_call_guidance>
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
- When applying standard React knowledge, favor generic tools for file reading and writing.
|
|
2487
|
-
- When applying Superblocks patterns, consider the specialized tools tailored to the platform.
|
|
2488
|
-
|
|
2489
|
-
<tool_choice_policy>
|
|
2490
|
-
- **NEVER** call \`build_addStateVar\` until:
|
|
2491
|
-
- At least one page component is **placed and bound**, **or**
|
|
2492
|
-
- You have an API whose \`response\` will be referenced in props.
|
|
2493
|
-
- If a value is already available from a **binding** or **API response**, **do not** create a StateVar to mirror it.
|
|
2494
|
-
- Create a StateVar only when you need:
|
|
2495
|
-
- Multi-source **derived** data reused in multiple props,
|
|
2496
|
-
- A **snapshot/history** separate from the live binding/API.
|
|
2497
|
-
</tool_choice_policy>
|
|
2498
|
-
|
|
2499
|
-
<use_parallel_tool_calls>
|
|
2500
|
-
For maximum efficiency, whenever you perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially. Prioritize calling tools in parallel whenever possible.
|
|
2501
|
-
</use_parallel_tool_calls>
|
|
2502
|
-
|
|
2503
|
-
<tool_call_input>
|
|
2504
|
-
- You MUST adhere to each tool's input schema
|
|
2505
|
-
- ALWAYS pass a JSON object for tool inputs. For tools with no parameters, use {} as the input object
|
|
2506
|
-
</tool_call_input>
|
|
596
|
+
**Parallel calls:** For maximum efficiency, invoke all independent operations simultaneously.
|
|
2507
597
|
|
|
598
|
+
**Tool inputs:** Adhere to each tool's input schema. Always pass JSON object for tool inputs.
|
|
2508
599
|
</tool_call_guidance>
|
|
2509
600
|
`);
|
|
2510
601
|
};
|