spine-framework 0.4.3 → 0.5.0-rc.4
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/.env.example +13 -3
- package/.framework/cli/commands/bootstrap.ts +392 -0
- package/.framework/cli/commands/dev.ts +91 -218
- package/.framework/cli/commands/doctor.ts +132 -26
- package/.framework/cli/commands/generate.ts +38 -298
- package/.framework/cli/commands/init.ts +20 -1
- package/.framework/cli/commands/migrate.ts +36 -30
- package/.framework/cli/commands/migrations.ts +16 -56
- package/.framework/cli/commands/upgrade.ts +35 -0
- package/.framework/cli/commands/validate-schema.ts +147 -0
- package/.framework/cli/context.ts +223 -110
- package/.framework/cli/index.ts +6 -0
- package/.framework/cli/lib/project-files.ts +241 -1
- package/.framework/functions/_shared/agent-runner.ts +1000 -81
- package/.framework/functions/_shared/cil.ts +2 -1
- package/.framework/functions/_shared/db.ts +3 -6
- package/.framework/functions/_shared/hash-api-key.ts +183 -79
- package/.framework/functions/_shared/kb.ts +61 -7
- package/.framework/functions/_shared/logging.ts +562 -224
- package/.framework/functions/_shared/message-sequence.ts +53 -0
- package/.framework/functions/_shared/middleware.ts +47 -44
- package/.framework/functions/_shared/pipeline-runner.ts +150 -37
- package/.framework/functions/_shared/principal.ts +9 -6
- package/.framework/functions/_shared/trigger-engine.ts +149 -40
- package/.framework/functions/admin-data.ts +48 -31
- package/.framework/functions/ai-agents.ts +103 -33
- package/.framework/functions/api-keys.ts +301 -122
- package/.framework/functions/apps.ts +43 -26
- package/.framework/functions/cil-ingest.ts +48 -15
- package/.framework/functions/debug-auth.ts +14 -8
- package/.framework/functions/deliveries.ts +39 -12
- package/.framework/functions/embeddings.ts +145 -76
- package/.framework/functions/integration-outbound.ts +67 -27
- package/.framework/functions/integrations.ts +254 -127
- package/.framework/functions/logs.ts +273 -133
- package/.framework/functions/observability.ts +277 -147
- package/.framework/functions/pipeline-executions.ts +1 -1
- package/.framework/functions/pipeline-runner-background.ts +120 -0
- package/.framework/functions/pipelines.ts +14 -9
- package/.framework/functions/prompt-configs.ts +7 -4
- package/.framework/functions/roles.ts +8 -5
- package/.framework/functions/security.ts +30 -17
- package/.framework/functions/system-cron.ts +250 -128
- package/.framework/functions/tests.ts +7 -6
- package/.framework/functions/timers.ts +310 -107
- package/.framework/functions/trigger-scheduler.ts +65 -5
- package/.framework/functions/triggers.ts +28 -13
- package/.framework/functions/types.ts +13 -10
- package/.framework/migrations/064_episode_leases.sql +196 -0
- package/.framework/migrations/065_execution_tables_updated_at.sql +20 -0
- package/.framework/migrations/066_episode_steal_thread_merge.sql +148 -0
- package/.framework/migrations/067_episode_lease_stale_steal.sql +93 -0
- package/.framework/migrations/068_cron_schedule_timer_rpcs.sql +251 -0
- package/.framework/migrations/069_log_stats_window.sql +54 -0
- package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
- package/.framework/migrations/071_timer_interval_keys.sql +96 -0
- package/.framework/migrations/072_slug_autofill.sql +77 -0
- package/.framework/migrations/073_seed_member_roles.sql +26 -0
- package/.framework/schemas/spine.config.schema.json +62 -0
- package/.framework/src/App.tsx +3 -1
- package/.framework/src/apps/admin/index.tsx +1141 -775
- package/.framework/src/components/ErrorBoundary.tsx +136 -0
- package/.framework/src/components/admin/AdminListPage.tsx +81 -73
- package/.framework/src/components/admin/AdminSidebar.tsx +92 -81
- package/.framework/src/components/admin/EntitySelect.tsx +181 -0
- package/.framework/src/components/admin/PageInstructions.tsx +86 -127
- package/.framework/src/components/app-shell/GenericAppShell.tsx +3 -1
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
- package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
- package/.framework/src/components/auth/TurnstileWidget.tsx +9 -6
- package/.framework/src/components/kb/KBArticleEditor.tsx +49 -6
- package/.framework/src/components/layout/Layout.tsx +4 -2
- package/.framework/src/components/layout/Sidebar.tsx +304 -179
- package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
- package/.framework/src/components/runtime/DataHeader.tsx +96 -48
- package/.framework/src/components/runtime/DataListPage.tsx +3 -26
- package/.framework/src/components/runtime/DataStats.tsx +10 -10
- package/.framework/src/components/runtime/DataTable.tsx +12 -10
- package/.framework/src/components/ui/sheet.tsx +24 -26
- package/.framework/src/components/ui/textarea.tsx +74 -48
- package/.framework/src/hooks/useBranding.ts +47 -0
- package/.framework/src/hooks/useEntityList.ts +12 -9
- package/.framework/src/hooks/useListSchema.ts +108 -58
- package/.framework/src/instructions/README.md +57 -0
- package/.framework/src/instructions/ai-agents.md +69 -0
- package/.framework/src/instructions/alerts.md +80 -0
- package/.framework/src/instructions/api-keys.md +87 -0
- package/.framework/src/instructions/apps.md +77 -0
- package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
- package/.framework/src/instructions/concepts/apps-and-renderers.md +162 -0
- package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
- package/.framework/src/instructions/concepts/automation.md +186 -0
- package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
- package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
- package/.framework/src/instructions/concepts/design-schema.md +152 -0
- package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
- package/.framework/src/instructions/concepts/observability.md +116 -0
- package/.framework/src/instructions/concepts/permissions.md +150 -0
- package/.framework/src/instructions/concepts/runtime-records.md +111 -0
- package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
- package/.framework/src/instructions/concepts/spine-way.md +172 -0
- package/.framework/src/instructions/deliveries.md +59 -0
- package/.framework/src/instructions/guide/00-orientation.md +106 -0
- package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
- package/.framework/src/instructions/guide/02-project-map.md +147 -0
- package/.framework/src/instructions/guide/03-domain-model.md +223 -0
- package/.framework/src/instructions/guide/04-first-app.md +227 -0
- package/.framework/src/instructions/guide/05-glossary.md +183 -0
- package/.framework/src/instructions/integrations.md +96 -0
- package/.framework/src/instructions/internal/cil-markers.md +83 -0
- package/.framework/src/instructions/invites.md +60 -0
- package/.framework/src/instructions/kb.md +80 -0
- package/.framework/src/instructions/logs.md +77 -0
- package/.framework/src/instructions/observability.md +61 -0
- package/.framework/src/instructions/operations/deployment.md +115 -0
- package/.framework/src/instructions/operations/migrations.md +125 -0
- package/.framework/src/instructions/operations/security-checklist.md +148 -0
- package/.framework/src/instructions/operations/troubleshooting.md +170 -0
- package/.framework/src/instructions/operations/upgrade.md +116 -0
- package/.framework/src/instructions/pipeline-executions.md +62 -0
- package/.framework/src/instructions/pipelines.md +78 -0
- package/.framework/src/instructions/prompts.md +95 -0
- package/.framework/src/instructions/reference/api/admin-data.md +162 -0
- package/.framework/src/instructions/reference/api/auth.md +113 -0
- package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
- package/.framework/src/instructions/reference/app-manifest.md +150 -0
- package/.framework/src/instructions/reference/cli.md +124 -0
- package/.framework/src/instructions/reference/database.md +132 -0
- package/.framework/src/instructions/reference/design-schema.md +280 -0
- package/.framework/src/instructions/reference/env-vars.md +133 -0
- package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
- package/.framework/src/instructions/reference/functions/admin-data.md +195 -0
- package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
- package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
- package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
- package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
- package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
- package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
- package/.framework/src/instructions/reference/functions/apps.md +140 -0
- package/.framework/src/instructions/reference/functions/auth.md +99 -0
- package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
- package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
- package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
- package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
- package/.framework/src/instructions/reference/functions/index.md +103 -0
- package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
- package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
- package/.framework/src/instructions/reference/functions/integrations.md +130 -0
- package/.framework/src/instructions/reference/functions/invites.md +182 -0
- package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
- package/.framework/src/instructions/reference/functions/kb.md +166 -0
- package/.framework/src/instructions/reference/functions/logs.md +141 -0
- package/.framework/src/instructions/reference/functions/observability.md +105 -0
- package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
- package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
- package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
- package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
- package/.framework/src/instructions/reference/functions/roles.md +121 -0
- package/.framework/src/instructions/reference/functions/security.md +140 -0
- package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
- package/.framework/src/instructions/reference/functions/system.md +106 -0
- package/.framework/src/instructions/reference/functions/tests.md +97 -0
- package/.framework/src/instructions/reference/functions/timers.md +117 -0
- package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
- package/.framework/src/instructions/reference/functions/triggers.md +140 -0
- package/.framework/src/instructions/reference/functions/types.md +146 -0
- package/.framework/src/instructions/reference/seeds.md +126 -0
- package/.framework/src/instructions/reference/shared-modules.md +127 -0
- package/.framework/src/instructions/reference/spine-config.md +114 -0
- package/.framework/src/instructions/roles.md +80 -0
- package/.framework/src/instructions/runtime-detail.md +70 -0
- package/.framework/src/instructions/runtime-list.md +77 -0
- package/.framework/src/instructions/security.md +67 -0
- package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
- package/.framework/src/instructions/tasks/add-a-role.md +96 -0
- package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
- package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
- package/.framework/src/instructions/tasks/add-a-type.md +151 -0
- package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
- package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
- package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
- package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
- package/.framework/src/instructions/tasks/package-and-install-an-app.md +127 -0
- package/.framework/src/instructions/tasks/test-your-code.md +108 -0
- package/.framework/src/instructions/tasks/write-a-function.md +132 -0
- package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
- package/.framework/src/instructions/testing.md +59 -0
- package/.framework/src/instructions/timers.md +82 -0
- package/.framework/src/instructions/triggers.md +69 -0
- package/.framework/src/instructions/types.md +93 -0
- package/.framework/src/lib/branding.ts +128 -0
- package/.framework/src/pages/NoAppPage.tsx +3 -1
- package/.framework/src/pages/NotFoundPage.tsx +62 -31
- package/.framework/src/pages/SetupPage.tsx +5 -2
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
- package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
- package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
- package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
- package/.framework/src/pages/admin/AppsPage.tsx +104 -66
- package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
- package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
- package/.framework/src/pages/admin/LogsPage.tsx +144 -76
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
- package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
- package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
- package/.framework/src/pages/admin/RolesPage.tsx +86 -43
- package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
- package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
- package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
- package/.framework/src/pages/admin/TimersPage.tsx +90 -62
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
- package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
- package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
- package/.framework/src/pages/admin/TypesPage.tsx +90 -48
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
- package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
- package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
- package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
- package/.framework/src/pages/auth/AuthConfirmPage.tsx +3 -1
- package/.framework/src/pages/auth/ForgotPasswordPage.tsx +3 -1
- package/.framework/src/pages/auth/LoginPage.tsx +4 -2
- package/.framework/src/pages/auth/RegisterPage.tsx +3 -1
- package/.framework/templates/agent/AGENTS.md +150 -0
- package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
- package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
- package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
- package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
- package/.framework/templates/agent/skills/spine-install-app/SKILL.md +72 -0
- package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
- package/.framework/templates/agent/skills/spine-new-app/SKILL.md +67 -0
- package/.framework/templates/agent/skills/spine-new-function/SKILL.md +65 -0
- package/.framework/templates/agent/skills/spine-new-type/SKILL.md +65 -0
- package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
- package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
- package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
- package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
- package/.framework/templates/project.json +5 -1
- package/.framework/tests/unit/account-scoping-gates.test.ts +392 -0
- package/.framework/tests/unit/agent-files.test.ts +355 -0
- package/.framework/tests/unit/agent-runner.test.ts +496 -47
- package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
- package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
- package/.framework/tests/unit/bootstrap.test.ts +242 -0
- package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
- package/.framework/tests/unit/debug-auth.test.ts +38 -0
- package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
- package/.framework/tests/unit/episode-lease.test.ts +695 -0
- package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
- package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
- package/.framework/tests/unit/kb-ingest-lease.test.ts +19 -0
- package/.framework/tests/unit/logging-app-id.test.ts +75 -0
- package/.framework/tests/unit/pipeline-runner-background.test.ts +120 -0
- package/.framework/tests/unit/pipeline-runner.test.ts +153 -1
- package/.framework/tests/unit/principal-cache.test.ts +15 -0
- package/.framework/tests/unit/system-context-denial.test.ts +145 -0
- package/.framework/tests/unit/system-cron.test.ts +240 -0
- package/.framework/tests/unit/timers.test.ts +159 -0
- package/.framework/tests/unit/trigger-engine.test.ts +278 -91
- package/.framework/tests/unit/trigger-scheduler.test.ts +78 -0
- package/README.md +125 -166
- package/config/tsconfig.cli.json +2 -2
- package/netlify.toml +5 -0
- package/package.json +15 -4
- package/templates/project/package-lock.json +2 -2
- package/.framework/docs/admin-routes.md +0 -76
- package/.framework/docs/api-docs-progress.md +0 -38
- package/.framework/docs/api-governance.md +0 -146
- package/.framework/docs/api-testing-results.md +0 -212
- package/.framework/docs/assembly-launch-guide.md +0 -138
- package/.framework/docs/audit-results.md +0 -590
- package/.framework/docs/db-api-inventory.md +0 -95
- package/.framework/docs/gap-remediation-backlog.md +0 -103
- package/.framework/docs/ui-adoption-verification.md +0 -111
- package/.framework/docs/ui-api-coverage.md +0 -84
- package/.framework/docs/v2-compatibility-audit.md +0 -228
- package/.framework/mcp/index.ts +0 -133
- package/.framework/mcp/load-env.ts +0 -60
- package/.framework/mcp/shared/app-manifest.ts +0 -275
- package/.framework/mcp/shared/db.ts +0 -477
- package/.framework/mcp/shared/kb.ts +0 -118
- package/.framework/mcp/tools/architect.ts +0 -431
- package/.framework/mcp/tools/cil/context.ts +0 -345
- package/.framework/mcp/tools/cil/graph.ts +0 -242
- package/.framework/mcp/tools/cil/ingest.ts +0 -1217
- package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
- package/.framework/mcp/tools/cil/pipeline.ts +0 -767
- package/.framework/mcp/tools/cil/project.ts +0 -791
- package/.framework/mcp/tools/cil/retrieval.ts +0 -336
- package/.framework/mcp/tools/cil/search-core.ts +0 -454
- package/.framework/mcp/tools/cil/search.ts +0 -97
- package/.framework/mcp/tools/cil/suggest.ts +0 -186
- package/.framework/mcp/tools/cil/validate.ts +0 -1369
- package/.framework/mcp/tools/kb/delete.ts +0 -139
- package/.framework/mcp/tools/kb/index.ts +0 -52
- package/.framework/mcp/tools/kb/ingest.ts +0 -84
- package/.framework/mcp/tools/kb/list-types.ts +0 -102
- package/.framework/mcp/tools/kb/publish.ts +0 -100
- package/.framework/mcp/tools/kb/reindex.ts +0 -86
- package/.framework/mcp/tools/kb/revoke.ts +0 -85
- package/.framework/mcp/tools/kb/search.ts +0 -101
- package/.framework/mcp/tools/kb/upsert.ts +0 -125
- package/.framework/mcp/tools/observability.ts +0 -510
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Timers
|
|
3
|
+
audience: both
|
|
4
|
+
kind: entity
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/timers.ts
|
|
9
|
+
- .framework/functions/system-cron.ts
|
|
10
|
+
- .framework/functions/trigger-scheduler.ts
|
|
11
|
+
- .framework/src/pages/admin/TimersPage.tsx
|
|
12
|
+
- .framework/src/pages/admin/TimerDetailPage.tsx
|
|
13
|
+
- .framework/migrations/068_cron_schedule_timer_rpcs.sql
|
|
14
|
+
- .framework/migrations/020_schema_reconciliation.sql
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What this is for
|
|
18
|
+
|
|
19
|
+
A **timer** is a scheduled job that invokes a pipeline when it comes due — Spine's recurring/one-off background task primitive. `system-cron` (invoked every minute by the `trigger-scheduler` Netlify scheduled function) asks the `get_due_timers` RPC for due timers and runs each one's pipeline.
|
|
20
|
+
|
|
21
|
+
## Fields and settings
|
|
22
|
+
|
|
23
|
+
- **Name** — string. Display name; required (`name` and `timer_type` are the API's required fields).
|
|
24
|
+
- **Type** — select: `interval`, `cron`, `once`. **Stored but not read by the scheduler** — it labels intent only; see Downstream effects.
|
|
25
|
+
- **App ID** — select, optional. Owning app; blank = account-scoped.
|
|
26
|
+
- **Pipeline ID** — select. The pipeline invoked when the timer fires. `get_due_timers` skips rows where `pipeline_id IS NULL`, so a blank pipeline means the timer never runs.
|
|
27
|
+
- **Active** — checkbox. Inactive timers are skipped by `get_due_timers`.
|
|
28
|
+
- **Description** — string, optional.
|
|
29
|
+
- **Config (JSON)** — the schedule. The only key the rescheduler reads today is `interval_minutes` (minutes between runs). The form seeds `{ "schedule": "", "interval_seconds": 3600 }` — neither key is honored by the current RPCs.
|
|
30
|
+
- **Metadata (JSON)** — optional metadata.
|
|
31
|
+
|
|
32
|
+
**Downstream effects**
|
|
33
|
+
|
|
34
|
+
- `get_due_timers` returns active timers with `pipeline_id IS NOT NULL` and `next_execution <= now()`. The timers API arms `next_execution` automatically on create/activate when `config.interval_minutes` (or `interval_seconds`) resolves to a positive interval — or pass `next_execution` explicitly.
|
|
35
|
+
- `update_timer_after_run` reschedules `next_execution = now() + interval` only when `config.interval_minutes` is a positive number; otherwise it sets `next_execution = NULL` and the timer becomes one-shot/dead. `timer_type`, `interval_seconds`, `schedule`, and `run_at` are ignored by the RPC.
|
|
36
|
+
- Each firing also stamps `last_execution` and increments `execution_count` / `success_count` / `failure_count` — the source of the list page's Executions and Success Rate columns.
|
|
37
|
+
- True cron expressions live on the separate `public.schedules` table (read by `get_due_schedules`), not on `timers` — a `cron`-type timer is a label, not a working cron.
|
|
38
|
+
|
|
39
|
+
## Minimal configuration
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"name": "Hourly sync",
|
|
44
|
+
"timer_type": "interval",
|
|
45
|
+
"pipeline_id": "<pipeline uuid>",
|
|
46
|
+
"is_active": true,
|
|
47
|
+
"config": { "interval_minutes": 60 }
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Full configuration
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"name": "Nightly-ish rollup",
|
|
56
|
+
"timer_type": "interval",
|
|
57
|
+
"pipeline_id": "<pipeline uuid>",
|
|
58
|
+
"is_active": true,
|
|
59
|
+
"config": { "interval_minutes": 1440 },
|
|
60
|
+
"metadata": { "owner": "platform", "notes": "fires ~daily once next_execution is seeded" }
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Example
|
|
65
|
+
|
|
66
|
+
A support app keeps an `interval` timer with `interval_minutes: 360` to re-check stale tickets every six hours, and a second timer whose `interval_minutes` was left blank to run a one-off backfill — after its first fire, `update_timer_after_run` writes `next_execution = NULL` so it never re-fires.
|
|
67
|
+
|
|
68
|
+
## Core files
|
|
69
|
+
|
|
70
|
+
- `.framework/functions/timers.ts` — API (list/get/create/update/delete; does not set `next_execution`)
|
|
71
|
+
- `.framework/functions/system-cron.ts` — per-tick driver: calls `get_due_timers`, runs `runPipeline` as a `timer:<id>` principal, then `update_timer_after_run`
|
|
72
|
+
- `.framework/functions/trigger-scheduler.ts` — Netlify scheduled entry point that delegates to `system-cron`
|
|
73
|
+
- `.framework/migrations/068_cron_schedule_timer_rpcs.sql` — the `get_due_timers` / `update_timer_after_run` RPCs
|
|
74
|
+
- `.framework/src/pages/admin/TimersPage.tsx` / `TimerDetailPage.tsx` — admin UI
|
|
75
|
+
- Table: `public.timers` (`timer_type`, `pipeline_id`, `next_execution`, `execution_count` added by migration 020)
|
|
76
|
+
|
|
77
|
+
## Agent notes
|
|
78
|
+
|
|
79
|
+
- The list's "Next Execution" column shows `Not scheduled` when `next_execution` is NULL — an active timer in that state cannot fire until it is re-armed (edit its interval or toggle it off/on).
|
|
80
|
+
- A timer with a blank `pipeline_id` saves fine but is filtered out by `get_due_timers` — always pick the pipeline.
|
|
81
|
+
- To arm a new timer, set `next_execution` in the DB (e.g. `now() + interval`); after the first successful run, `interval_minutes` keeps it rescheduling.
|
|
82
|
+
- "Success Rate" is `success_count / execution_count`; failures still reschedule — a broken pipeline does not stop the cadence.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Triggers
|
|
3
|
+
audience: both
|
|
4
|
+
kind: entity
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/triggers.ts
|
|
9
|
+
- .framework/functions/_shared/trigger-engine.ts
|
|
10
|
+
- .framework/functions/trigger-scheduler.ts
|
|
11
|
+
- .framework/src/pages/admin/TriggersPage.tsx
|
|
12
|
+
- .framework/src/pages/admin/TriggerDetailPage.tsx
|
|
13
|
+
- .framework/migrations/064_episode_leases.sql
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## What this is for
|
|
17
|
+
|
|
18
|
+
A **trigger** listens for a Spine event and, when active, hands the event payload to a pipeline for processing. It is the link between emitted platform events (e.g. `item_created`) and the workflows that react to them.
|
|
19
|
+
|
|
20
|
+
## Fields and settings
|
|
21
|
+
|
|
22
|
+
- **Name** — string. Display name; missing name prevents saving.
|
|
23
|
+
- **Event Type** — string. The event to listen to, e.g. `item_created`. Must match the emitted event name **exactly** — a typo means the trigger never fires.
|
|
24
|
+
- **App ID** — select, optional. Owning app; blank = account-scoped.
|
|
25
|
+
- **Pipeline ID** — select, required at create (`name`, `event_type`, `pipeline_id` are all required by the API). The pipeline invoked when the trigger fires; a stale ID means the queued execution fails instead of running.
|
|
26
|
+
- **Active** — checkbox. Inactive triggers are skipped at fire time — matching events produce no run.
|
|
27
|
+
- **Description** — string, optional.
|
|
28
|
+
- **Config (JSON)** — trigger-engine configuration, e.g. filters or payload mapping.
|
|
29
|
+
- **Metadata (JSON)** — optional metadata.
|
|
30
|
+
|
|
31
|
+
**Downstream effects**
|
|
32
|
+
|
|
33
|
+
- The trigger engine (`admin-data` writes → `_shared/trigger-engine`) matches emitted events to active triggers by `event_type`; real event names use underscores — `item_created`, `item_updated`, `item_deleted`, `thread_created`, `message_created`, etc. (see the `EventType` union in `_shared/trigger-engine.ts`).
|
|
34
|
+
- `config` filters narrow which matching events actually invoke the pipeline.
|
|
35
|
+
- A wrong or stale `pipeline_id` means the trigger fires, queues an execution row, and the run fails — check Pipeline Executions, not just the trigger, when "nothing happened".
|
|
36
|
+
|
|
37
|
+
## Minimal configuration
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"name": "On item created",
|
|
42
|
+
"event_type": "item_created",
|
|
43
|
+
"pipeline_id": "<pipeline uuid>",
|
|
44
|
+
"is_active": true
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Full configuration
|
|
49
|
+
|
|
50
|
+
Adds `config` with payload filters (e.g. match only `item_created` where `type_slug = "ticket"`), field mappings into the pipeline input, and `metadata` documenting the contract the event producer is expected to emit.
|
|
51
|
+
|
|
52
|
+
## Example
|
|
53
|
+
|
|
54
|
+
A support app fires `item_created` filtered to `type_slug = "ticket"`; the trigger invokes its triage pipeline, which assigns the ticket and posts a thread message. A second trigger on `item_updated` handles SLA warnings — both share the same pipeline pattern but different filters.
|
|
55
|
+
|
|
56
|
+
## Core files
|
|
57
|
+
|
|
58
|
+
- `.framework/functions/triggers.ts` — API
|
|
59
|
+
- `.framework/functions/_shared/trigger-engine.ts` — event matching and dispatch to `pipeline-runner-background`
|
|
60
|
+
- `.framework/functions/trigger-scheduler.ts` — Netlify scheduled entry point (delegates to `system-cron`)
|
|
61
|
+
- `.framework/src/pages/admin/TriggersPage.tsx` / `TriggerDetailPage.tsx` — admin UI
|
|
62
|
+
- Table: `public.triggers`
|
|
63
|
+
|
|
64
|
+
## Agent notes
|
|
65
|
+
|
|
66
|
+
- Event names are `<entity>_<past tense>` (`item_created`, `message_deleted`, ...) — the Event Type input's own placeholder still shows the stale `item.created` form; use underscores.
|
|
67
|
+
- `config` filters (`entity_type`, `type_slug`, `filters` with `$eq`/`$ne`/`$gt`/etc.) narrow which matching events actually invoke the pipeline.
|
|
68
|
+
- The list's "Total Fires" stat and "Fires"/"Last Fired" columns count trigger firings (the `record_trigger_fire` RPC), not successful pipeline runs — a trigger can fire while its pipeline fails.
|
|
69
|
+
- `config` must be a JSON **object**, not a string — the second most common save failure after event-type typos.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Types
|
|
3
|
+
audience: both
|
|
4
|
+
kind: entity
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/types.ts
|
|
9
|
+
- .framework/src/pages/admin/TypesPage.tsx
|
|
10
|
+
- .framework/src/pages/admin/TypeDetailPage.tsx
|
|
11
|
+
- .framework/functions/_shared/permissions.ts
|
|
12
|
+
- .framework/functions/_shared/resolve-ids.ts
|
|
13
|
+
- .framework/functions/_shared/schema-utils.ts
|
|
14
|
+
- .framework/cli/commands/install-app.ts
|
|
15
|
+
- .framework/migrations/015_types_kind_slug_unique.sql
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## What this is for
|
|
19
|
+
|
|
20
|
+
A **type** is the design schema for a runtime record kind. It defines the fields, default list/detail views, record permissions, icon, and color that the generic admin and runtime UIs use to create, validate, and display records. Every `items`/`threads`/`messages`/etc. record is stamped with its type's `design_schema` at creation.
|
|
21
|
+
|
|
22
|
+
## Fields and settings
|
|
23
|
+
|
|
24
|
+
- **Name** — string. Display label for the type. Empty name blocks save.
|
|
25
|
+
- **Slug** — string. URL-friendly identifier, unique per `kind`. Callers resolve types by slug; duplicates break `resolveTypeId`/`.single()` lookups. Rarely change after records exist.
|
|
26
|
+
- **Description** — string, optional. Human-readable context.
|
|
27
|
+
- **Kind** — select: `item`, `account`, `person`, `thread`, `message`, `link`, `attachment`, `watcher`. Determines which runtime table the type governs.
|
|
28
|
+
- **Ownership** — select: `system` (platform types, protected from deletion), `app` (owned by an app), `tenant` (account-specific).
|
|
29
|
+
- **Is Active** — checkbox. Inactive types are hidden from lists and cannot have records created.
|
|
30
|
+
- **App ID** — select. Required whenever ownership is not `system` — the form rejects save otherwise.
|
|
31
|
+
- **Icon / Color** — UI presentation stored on the `types` row.
|
|
32
|
+
- **Design Schema (JSON)** — the full schema: `fields`, `views` (`default_list`, `default_detail`), `record_permissions`, `scope`, `functionality`.
|
|
33
|
+
- **Validation Schema** — read-only, regenerated from `design_schema` on every save by the API.
|
|
34
|
+
|
|
35
|
+
**Downstream effects**
|
|
36
|
+
|
|
37
|
+
- Generic list/detail pages render directly from `views`. A missing `default_list`/`default_detail` or unknown `display_type` breaks those pages.
|
|
38
|
+
- `record_permissions` is the **base, most restrictive grant**: if a role can read/update a record, all fields default to read/write. `fields.*.permissions` only *loosen* access per field or restrict `system: true` fields.
|
|
39
|
+
- `system: true` fields are read from `record.<field>`; non-system fields from `record.data.<field>`.
|
|
40
|
+
- Changing `data_type` on a field with existing records does not migrate data — old values remain until rewritten.
|
|
41
|
+
- Type seeds use `conflict: "kind,slug"` with `inject_app_id: false` — types are global, not duplicated per app install.
|
|
42
|
+
|
|
43
|
+
## Minimal configuration
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"scope": "account",
|
|
48
|
+
"fields": {
|
|
49
|
+
"title": { "data_type": "text", "label": "Title", "required": true, "system": true }
|
|
50
|
+
},
|
|
51
|
+
"views": {
|
|
52
|
+
"default_list": {
|
|
53
|
+
"type": "list", "display": "table", "label": "List",
|
|
54
|
+
"fields": {
|
|
55
|
+
"title": { "sortable": true, "display_type": "text" },
|
|
56
|
+
"created_at": { "sortable": true, "display_type": "timestamp" }
|
|
57
|
+
},
|
|
58
|
+
"default_sort": { "field": "created_at", "direction": "desc" }
|
|
59
|
+
},
|
|
60
|
+
"default_detail": {
|
|
61
|
+
"type": "detail", "label": "Detail",
|
|
62
|
+
"sections": [{ "title": "Details", "fields": { "title": { "display_type": "input" } } }]
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"record_permissions": { "system_admin": ["create", "read", "update", "delete"] },
|
|
66
|
+
"functionality": null
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Full configuration
|
|
71
|
+
|
|
72
|
+
Adds typed fields, select `options`, per-field `permissions` overrides, multiple views with sorting/badges, and per-role record grants (`admin`, `member`). See the [design schema reference](reference/design-schema.md) for the full `data_type` list (`text`, `number`, `select`, `multiselect`, `reference`, `json`, `date`, `boolean`, ...) and `display_type` list (`badge`, `timestamp`, `select`, `currency`, ...).
|
|
73
|
+
|
|
74
|
+
## Example
|
|
75
|
+
|
|
76
|
+
A support app defines a `ticket` type (kind `item`, ownership `app`): `title` (system text), `status` (select: open/pending/closed), `priority` (select), `assignee_id` (reference). `record_permissions` grants `system_admin` all actions, `admin` create/read/update, `member` read. `fields.assignee_id.permissions` loosens `write` to `member` so agents can self-assign.
|
|
77
|
+
|
|
78
|
+
## Core files
|
|
79
|
+
|
|
80
|
+
- `.framework/functions/types.ts` — API (list/get/create/update/delete; regenerates `validation_schema`)
|
|
81
|
+
- `.framework/src/pages/admin/TypesPage.tsx` / `TypeDetailPage.tsx` — admin UI
|
|
82
|
+
- `.framework/src/components/app-shell/GenericListPage.tsx` / `GenericDetailPage.tsx` — generic renderer
|
|
83
|
+
- `.framework/functions/_shared/permissions.ts` — record/field permission evaluation
|
|
84
|
+
- [Design schema reference](reference/design-schema.md) — full field/view schema reference in this book
|
|
85
|
+
- Table: `public.types`
|
|
86
|
+
|
|
87
|
+
## Agent notes
|
|
88
|
+
|
|
89
|
+
- Never hand-edit `validation_schema`; change `design_schema` and the API regenerates it.
|
|
90
|
+
- Slug uniqueness is per `(kind, slug)` — reuse across kinds is fine, reuse within a kind breaks lookups.
|
|
91
|
+
- Every field needs `data_type`, `label`, `required`; `select`/`multiselect`/`radio` also need `options`.
|
|
92
|
+
- Don't add `fields.*.permissions` unless overriding the base grant — the common mistake is re-stating record-level permissions per field.
|
|
93
|
+
- Custom apps CRUD runtime records through `/api/admin-data` — never custom wrappers.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// CIL[BEGIN stable_id="~/.framework/src/lib/branding.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/src/lib/branding.ts"]
|
|
2
|
+
// CIL[DOC-MACRO purpose="Instance branding resolved from spine.config.json — app name, logo, favicon, document title"]
|
|
3
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Load the optional branding block from /spine.config.json once per session, merge it over Spine defaults, and apply favicon/title to the document"]
|
|
4
|
+
// CIL[DOC-CONTEXT module="framework-api" layer="lib" related_files=".framework/src/hooks/useBranding.ts,spine.config.json,.framework/index.html"]
|
|
5
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="SpineBranding resolved object; applyBrandingToDocument performs DOM mutation" errors="fetch or parse failures fall back to DEFAULT_BRANDING" side_effects="HTTP GET /spine.config.json (single cached request); document.title and link[rel=icon] mutation"]
|
|
6
|
+
// CIL[DOC-MICRO logic_summary="Module-level cache plus single inflight fetch; resolveBranding fills missing keys with defaults; applyBrandingToDocument sets document.title and swaps the favicon link href" notable_decisions="Branding lives in instance-owned spine.config.json so framework upgrades can never overwrite app branding; index.html keeps the Spine default and the runtime swap handles app-branded instances"]
|
|
7
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
8
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
9
|
+
// CIL[DOC-AUDIENCE audience="framework developers"]
|
|
10
|
+
// CIL[DOC-NOTE type="contract" content="branding is a top-level sibling of registration in spine.config.json. All keys optional; absent block yields DEFAULT_BRANDING so bare framework installs stay Spine-branded."]
|
|
11
|
+
// CIL[END stable_id="~/.framework/src/lib/branding.ts:module_header:module_header"]
|
|
12
|
+
|
|
13
|
+
// CIL[BEGIN stable_id="~/.framework/src/lib/branding.ts:spinebranding:interface" type="interface" identity="SpineBranding" parent="~/.framework/src/lib/branding.ts"]
|
|
14
|
+
// CIL[DOC-MACRO purpose="Resolved instance branding shape"]
|
|
15
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Type for the branding block after defaults are applied"]
|
|
16
|
+
// CIL[DOC-CONTEXT module="framework-api" layer="lib" related_files=".framework/src/lib/branding.ts"]
|
|
17
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="SpineBranding object" errors="none" side_effects="none"]
|
|
18
|
+
// CIL[DOC-MICRO logic_summary="app_name drives headings and logo alt; title drives document.title and wordmark text; logo_url and favicon_url are public/ asset paths" notable_decisions="title falls back to app_name, then to 'Spine Framework' when no branding block exists"]
|
|
19
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
20
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
21
|
+
export interface SpineBranding {
|
|
22
|
+
app_name: string
|
|
23
|
+
title: string
|
|
24
|
+
logo_url: string
|
|
25
|
+
favicon_url: string
|
|
26
|
+
}
|
|
27
|
+
// CIL[END stable_id="~/.framework/src/lib/branding.ts:spinebranding:interface"]
|
|
28
|
+
|
|
29
|
+
// CIL[BEGIN stable_id="~/.framework/src/lib/branding.ts:default_branding:constant" type="constant" identity="DEFAULT_BRANDING" parent="~/.framework/src/lib/branding.ts"]
|
|
30
|
+
// CIL[DOC-MACRO purpose="Spine Framework default branding used when spine.config.json has no branding block"]
|
|
31
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Provide the fallback brand so the framework is self-branded out of the box"]
|
|
32
|
+
// CIL[DOC-CONTEXT module="framework-api" layer="lib" related_files=".framework/src/lib/branding.ts,.framework/index.html"]
|
|
33
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="SpineBranding constant" errors="none" side_effects="none"]
|
|
34
|
+
// CIL[DOC-MICRO logic_summary="Matches the static values in .framework/index.html (title 'Spine Framework', favicon /spine-logo.svg)" notable_decisions="[]"]
|
|
35
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
36
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
37
|
+
export const DEFAULT_BRANDING: SpineBranding = {
|
|
38
|
+
app_name: 'Spine',
|
|
39
|
+
title: 'Spine Framework',
|
|
40
|
+
logo_url: '/spine-logo.svg',
|
|
41
|
+
favicon_url: '/spine-logo.svg',
|
|
42
|
+
}
|
|
43
|
+
// CIL[END stable_id="~/.framework/src/lib/branding.ts:default_branding:constant"]
|
|
44
|
+
|
|
45
|
+
let cached: SpineBranding | null = null
|
|
46
|
+
let inflight: Promise<SpineBranding> | null = null
|
|
47
|
+
|
|
48
|
+
// CIL[BEGIN stable_id="~/.framework/src/lib/branding.ts:resolvebranding:function" type="function" identity="resolveBranding" parent="~/.framework/src/lib/branding.ts"]
|
|
49
|
+
// CIL[DOC-MACRO purpose="Merge a raw branding block over defaults"]
|
|
50
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Produce a complete SpineBranding from a possibly partial branding config value"]
|
|
51
|
+
// CIL[DOC-CONTEXT module="framework-api" layer="lib" related_files=".framework/src/lib/branding.ts"]
|
|
52
|
+
// CIL[DOC-CONTRACT inputs="raw: unknown (config.branding)" outputs="SpineBranding" errors="none" side_effects="none"]
|
|
53
|
+
// CIL[DOC-MICRO logic_summary="Each key falls back independently; title falls back to the resolved app_name so app-branded instances get a sane tab title without duplicating config" notable_decisions="non-string values treated as absent"]
|
|
54
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
55
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
56
|
+
function resolveBranding(raw: unknown): SpineBranding {
|
|
57
|
+
const b = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>
|
|
58
|
+
const appName = typeof b.app_name === 'string' && b.app_name ? b.app_name : DEFAULT_BRANDING.app_name
|
|
59
|
+
const configuredTitle = typeof b.title === 'string' && b.title ? b.title : null
|
|
60
|
+
return {
|
|
61
|
+
app_name: appName,
|
|
62
|
+
title: configuredTitle ?? (b.app_name ? appName : DEFAULT_BRANDING.title),
|
|
63
|
+
logo_url: typeof b.logo_url === 'string' && b.logo_url ? b.logo_url : DEFAULT_BRANDING.logo_url,
|
|
64
|
+
favicon_url:
|
|
65
|
+
typeof b.favicon_url === 'string' && b.favicon_url ? b.favicon_url : DEFAULT_BRANDING.favicon_url,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// CIL[END stable_id="~/.framework/src/lib/branding.ts:resolvebranding:function"]
|
|
69
|
+
|
|
70
|
+
// CIL[BEGIN stable_id="~/.framework/src/lib/branding.ts:loadbranding:function" type="function" identity="loadBranding" parent="~/.framework/src/lib/branding.ts"]
|
|
71
|
+
// CIL[DOC-MACRO purpose="Fetch and cache the instance branding from /spine.config.json"]
|
|
72
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Perform a single fetch per session and memoize the resolved branding"]
|
|
73
|
+
// CIL[DOC-CONTEXT module="framework-api" layer="lib" related_files=".framework/src/lib/branding.ts,.framework/src/hooks/useBranding.ts"]
|
|
74
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Promise<SpineBranding>" errors="network/parse failures resolve to DEFAULT_BRANDING" side_effects="one HTTP GET /spine.config.json per session"]
|
|
75
|
+
// CIL[DOC-MICRO logic_summary="Returns cached value if present; otherwise shares one inflight promise across callers; any failure caches defaults so a missing config never retries per component" notable_decisions="[]"]
|
|
76
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
77
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
78
|
+
export function loadBranding(): Promise<SpineBranding> {
|
|
79
|
+
if (cached) return Promise.resolve(cached)
|
|
80
|
+
if (!inflight) {
|
|
81
|
+
inflight = fetch('/spine.config.json')
|
|
82
|
+
.then(res => (res.ok ? res.json() : null))
|
|
83
|
+
.then(config => {
|
|
84
|
+
cached = resolveBranding(config?.branding)
|
|
85
|
+
return cached
|
|
86
|
+
})
|
|
87
|
+
.catch(() => {
|
|
88
|
+
cached = DEFAULT_BRANDING
|
|
89
|
+
return cached
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
return inflight
|
|
93
|
+
}
|
|
94
|
+
// CIL[END stable_id="~/.framework/src/lib/branding.ts:loadbranding:function"]
|
|
95
|
+
|
|
96
|
+
// CIL[BEGIN stable_id="~/.framework/src/lib/branding.ts:getcachedbranding:function" type="function" identity="getCachedBranding" parent="~/.framework/src/lib/branding.ts"]
|
|
97
|
+
// CIL[DOC-MACRO purpose="Synchronous read of resolved branding, or defaults before the fetch lands"]
|
|
98
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Give render paths a branding value without awaiting"]
|
|
99
|
+
// CIL[DOC-CONTEXT module="framework-api" layer="lib" related_files=".framework/src/lib/branding.ts"]
|
|
100
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="SpineBranding" errors="none" side_effects="none"]
|
|
101
|
+
// CIL[DOC-MICRO logic_summary="cached ?? DEFAULT_BRANDING" notable_decisions="[]"]
|
|
102
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
103
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
104
|
+
export function getCachedBranding(): SpineBranding {
|
|
105
|
+
return cached ?? DEFAULT_BRANDING
|
|
106
|
+
}
|
|
107
|
+
// CIL[END stable_id="~/.framework/src/lib/branding.ts:getcachedbranding:function"]
|
|
108
|
+
|
|
109
|
+
// CIL[BEGIN stable_id="~/.framework/src/lib/branding.ts:applybrandingtodocument:function" type="function" identity="applyBrandingToDocument" parent="~/.framework/src/lib/branding.ts"]
|
|
110
|
+
// CIL[DOC-MACRO purpose="Apply resolved branding to document.title and the favicon link"]
|
|
111
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Swap the framework-shipped favicon/title for instance branding at runtime"]
|
|
112
|
+
// CIL[DOC-CONTEXT module="framework-api" layer="lib" related_files=".framework/src/lib/branding.ts,.framework/index.html"]
|
|
113
|
+
// CIL[DOC-CONTRACT inputs="branding: SpineBranding" outputs="void" errors="none" side_effects="sets document.title; rewrites or creates link[rel~=icon] in document.head"]
|
|
114
|
+
// CIL[DOC-MICRO logic_summary="Idempotent DOM mutation — safe to call on every branding state change from every consumer" notable_decisions="runtime swap keeps .framework/index.html a static framework-owned file that upgrades can replace freely"]
|
|
115
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
116
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
117
|
+
export function applyBrandingToDocument(branding: SpineBranding): void {
|
|
118
|
+
if (branding.title) document.title = branding.title
|
|
119
|
+
if (!branding.favicon_url) return
|
|
120
|
+
let link = document.querySelector<HTMLLinkElement>('link[rel~="icon"]')
|
|
121
|
+
if (!link) {
|
|
122
|
+
link = document.createElement('link')
|
|
123
|
+
link.rel = 'icon'
|
|
124
|
+
document.head.appendChild(link)
|
|
125
|
+
}
|
|
126
|
+
link.href = branding.favicon_url
|
|
127
|
+
}
|
|
128
|
+
// CIL[END stable_id="~/.framework/src/lib/branding.ts:applybrandingtodocument:function"]
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import React from 'react'
|
|
12
12
|
import { useAuth } from '../contexts/AuthContext'
|
|
13
|
+
import { useBranding } from '../hooks/useBranding'
|
|
13
14
|
// CIL[END stable_id="~/.framework/src/pages/NoAppPage.tsx:module_header:module_header"]
|
|
14
15
|
|
|
15
16
|
// CIL[BEGIN stable_id="~/.framework/src/pages/NoAppPage.tsx:noapppage:component" type="component" identity="NoAppPage" parent="~/.framework/src/pages/NoAppPage.tsx"]
|
|
@@ -22,12 +23,13 @@ import { useAuth } from '../contexts/AuthContext'
|
|
|
22
23
|
// CIL[DOC-STABILITY stability="experimental"]
|
|
23
24
|
export function NoAppPage() {
|
|
24
25
|
const { logout } = useAuth()
|
|
26
|
+
const branding = useBranding()
|
|
25
27
|
|
|
26
28
|
return (
|
|
27
29
|
<div className="min-h-screen flex items-center justify-center bg-slate-50 px-4">
|
|
28
30
|
<div className="max-w-md w-full text-center space-y-6">
|
|
29
31
|
<div className="mx-auto flex items-center justify-center">
|
|
30
|
-
<img src=
|
|
32
|
+
<img src={branding.logo_url} alt={branding.app_name} className="h-14 w-auto" />
|
|
31
33
|
</div>
|
|
32
34
|
<div className="space-y-2">
|
|
33
35
|
<h1 className="text-2xl font-semibold text-slate-900">No apps yet</h1>
|
|
@@ -1,35 +1,65 @@
|
|
|
1
1
|
// CIL[BEGIN stable_id="~/.framework/src/pages/NotFoundPage.tsx:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/src/pages/NotFoundPage.tsx"]
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
/**
|
|
3
|
+
* @module NotFoundPage
|
|
4
|
+
* @audience both
|
|
5
|
+
* @layer shared-core
|
|
6
|
+
* @stability stable
|
|
7
|
+
*
|
|
8
|
+
* This module renders a Not Found page for the application, providing users with a friendly message when they navigate to an undefined route. It serves as a fallback UI component, enhancing user experience by guiding them back to the home page. INVARIANT: The Not Found page should always be accessible for any invalid routes.
|
|
9
|
+
*
|
|
10
|
+
* @seeAlso react-router-dom (provides routing capabilities).
|
|
11
|
+
* @seeAlso lucide-react (used for icons).
|
|
12
|
+
*
|
|
13
|
+
* @cil-generated
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
10
25
|
|
|
11
26
|
|
|
12
27
|
|
|
13
28
|
import React from 'react'
|
|
14
29
|
import { Link } from 'react-router-dom'
|
|
15
30
|
import { Home } from 'lucide-react';
|
|
16
|
-
// CIL[DOC-VECTOR type="context" content="purpose='Import dependencies used by the NotFoundPage 404 component'; No verified external callers; imports: none"]
|
|
17
|
-
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
18
|
-
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
19
|
-
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
20
|
-
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
21
|
-
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
22
31
|
// CIL[END stable_id="~/.framework/src/pages/NotFoundPage.tsx:module_header:module_header"]
|
|
23
32
|
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
24
51
|
// CIL[BEGIN stable_id="~/.framework/src/pages/NotFoundPage.tsx:NotFoundPage:component" type="component" identity="NotFoundPage" parent="~/.framework/src/pages/NotFoundPage.tsx"]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The NotFoundPage component displays a 404 error message when a page is not found, providing a user-friendly interface to guide users back to the home page.
|
|
54
|
+
*
|
|
55
|
+
* @macro React functional component responsible for rendering a 404 error page.
|
|
56
|
+
* @micro Utilizes JSX to structure the layout and styling of the error message and navigation link.
|
|
57
|
+
* @outputSpec JSX.Element
|
|
58
|
+
* @sideEffects none
|
|
59
|
+
* @stability internal
|
|
60
|
+
* @calls Link
|
|
61
|
+
* @cil-generated
|
|
62
|
+
*/
|
|
33
63
|
export function NotFoundPage() {
|
|
34
64
|
return (
|
|
35
65
|
<div className="min-h-screen bg-slate-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
|
@@ -43,11 +73,11 @@ export function NotFoundPage() {
|
|
|
43
73
|
|
|
44
74
|
<div className="mt-6">
|
|
45
75
|
<Link
|
|
46
|
-
to="/
|
|
76
|
+
to="/"
|
|
47
77
|
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
48
78
|
>
|
|
49
79
|
<Home className="w-4 h-4 mr-2" />
|
|
50
|
-
Go
|
|
80
|
+
Go home
|
|
51
81
|
</Link>
|
|
52
82
|
</div>
|
|
53
83
|
</div>
|
|
@@ -55,10 +85,11 @@ export function NotFoundPage() {
|
|
|
55
85
|
</div>
|
|
56
86
|
)
|
|
57
87
|
}
|
|
58
|
-
// CIL[
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
88
|
+
// CIL[END stable_id="~/.framework/src/pages/NotFoundPage.tsx:NotFoundPage:component"]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import React from 'react'
|
|
12
12
|
import { Link } from 'react-router-dom'
|
|
13
|
+
import { useBranding } from '../hooks/useBranding'
|
|
13
14
|
// CIL[DOC-VECTOR type="context" content="purpose='Declare module-level imports for the Setup page'; No verified external callers; imports: none"]
|
|
14
15
|
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
15
16
|
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
@@ -28,15 +29,17 @@ import { Link } from 'react-router-dom'
|
|
|
28
29
|
// CIL[DOC-STABILITY stable]
|
|
29
30
|
|
|
30
31
|
export function SetupPage() {
|
|
32
|
+
const branding = useBranding()
|
|
33
|
+
|
|
31
34
|
return (
|
|
32
35
|
<div className="min-h-screen flex items-center justify-center bg-slate-50 py-12 px-4 sm:px-6 lg:px-8">
|
|
33
36
|
<div className="max-w-lg w-full space-y-8">
|
|
34
37
|
<div className="text-center">
|
|
35
38
|
<div className="mx-auto flex items-center justify-center">
|
|
36
|
-
<img src=
|
|
39
|
+
<img src={branding.logo_url} alt={branding.app_name} className="h-14 w-auto" />
|
|
37
40
|
</div>
|
|
38
41
|
<h2 className="mt-6 text-3xl font-extrabold text-slate-900">
|
|
39
|
-
Welcome to
|
|
42
|
+
Welcome to {branding.app_name}
|
|
40
43
|
</h2>
|
|
41
44
|
<p className="mt-2 text-sm text-slate-600">
|
|
42
45
|
Your project is running but no apps are installed yet.
|