diff --git a/.changeset/prune-aspirational-config-3494.md b/.changeset/prune-aspirational-config-3494.md new file mode 100644 index 0000000000..f1f861e367 --- /dev/null +++ b/.changeset/prune-aspirational-config-3494.md @@ -0,0 +1,56 @@ +--- +"@objectstack/spec": minor +--- + +feat(spec)!: prune the still-dead aspirational config from Theme / Translation / Webhook (#3494) + +Removes the authorable-but-never-consumed props confirmed dead by the 2026-06 +liveness audit (follow-up to #1878/#1893; same treatment as the #2377 and +#3464 prunes). Authoring any of these was a silent no-op. + +## Removed + +**Theme** (`ThemeSchema`) — the theme engine (objectui `generateThemeVars`) +never emitted or consumed them: +- Props: `spacing`, `breakpoints`, `logo`, `density`, `wcagContrast`, `rtl`, + `touchTarget`, `keyboardNavigation` +- Exports: `SpacingSchema`, `BreakpointsSchema`, `DensityModeSchema` (+ + deprecated `DensityMode` alias), `WcagContrastLevelSchema` (+ deprecated + `WcagContrastLevel` alias), and the `Spacing` / `Breakpoints` / + `DensityMode` / `WcagContrastLevel` types + +**Translation** (`TranslationConfigSchema`) — no runtime reader; there is no +ICU engine and interpolation is always simple `{variable}` substitution: +- Props: `fileOrganization`, `messageFormat`, `lazyLoad`, `cache` +- Exports: `MessageFormatSchema`, `TranslationFileOrganizationSchema`, and the + `MessageFormat` / `TranslationFileOrganization` types + +**Webhook** (`WebhookSchema`) — the delivery path always sends its own fixed +envelope and only applies HMAC signing via `secret`; delivery retries are owned +by the messaging outbox's fixed schedule: +- Props: `body`, `payloadFields`, `includeSession`, `authentication` + (bearer/basic/api-key were never attached; HMAC via `secret` stays), + `retryPolicy`, `tags` +- Exports: the entire inbound `WebhookReceiverSchema` + `WebhookReceiver` type + (never consumed by any runtime) + +## Migration + +Delete these keys from your configs — they never did anything, so removing +them changes no behavior. Parsed output no longer contains the previously +defaulted keys (`includeSession: false`, `fileOrganization: 'per_locale'`, +`messageFormat: 'simple'`, `lazyLoad: false`, `cache: true`). Webhook HMAC +signing (`secret`), `headers`, and `timeoutMs` are unaffected. File layout for +translations remains a pure authoring convention — no config knob needed. + +## Deliberately NOT removed + +- Translation `supportedLocales` — it has a live reader (pinyin-search + capability toggle in `serve.ts`). +- Job `retryPolicy` / `timeout` — being implemented (built, not pruned) in the + #3494 follow-up PR. +- The materialized webhook props (`name`, `object`, `triggers`, `url`, + `method`, `headers`, `timeoutMs`, `secret`, `isActive`, `description`, + `label`) — live via the #3489 bridge; ledger flip tracked in #3490. + +Refs #3494, #1878, #1893. diff --git a/content/docs/automation/webhooks.mdx b/content/docs/automation/webhooks.mdx index 8746657a35..6548cde80b 100644 --- a/content/docs/automation/webhooks.mdx +++ b/content/docs/automation/webhooks.mdx @@ -514,7 +514,7 @@ Four phases. Each phase delivers visible user value. > `system/*` Zod schemas. There is no `system/webhook-delivery.zod.ts` and no > `SysWebhookSchema` / `SysWebhookDeliverySchema` / > `WebhookSignatureHeaderSchema` / `WebhookEventPayloadSchema`. Instead it -> reused the existing `WebhookSchema` (and `WebhookReceiverSchema`) in +> reused the existing `WebhookSchema` in > `automation/webhook.zod.ts`, defined `sys_webhook` as an > `ObjectSchema.create()` in the plugin, and reused > service-messaging's `sys_http_delivery` outbox for deliveries (ADR-0018 M3). @@ -555,8 +555,9 @@ Four phases. Each phase delivers visible user value. ## 16. Non-goals (v1) - **Inbound webhooks.** Receiving HTTP from third parties is a separate - protocol (`WebhookReceiverSchema` already exists). The two systems - share no runtime. + protocol (the aspirational `WebhookReceiverSchema` was removed unbuilt in + #3494 — reintroduce it only alongside a real inbound runtime). The two + systems share no runtime. - **Webhook chaining / fan-in.** No "if receiver returns a payload, emit a new event". Out of scope. - **GraphQL subscriptions / WebSocket fan-out.** Different transport, diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index 1b726ea9e8..61293896d9 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -157,7 +157,7 @@ Flows, state machines, approvals, and integrations. | **[Flow](/docs/references/automation/flow)** | `flow.zod.ts` | Flow, FlowNode | Visual workflow builder | | **[Approval](/docs/references/automation/approval)** | `approval.zod.ts` | ApprovalNodeConfig | Flow approval-node config | | **[State Machine](/docs/references/automation/state-machine)** | `state-machine.zod.ts` | StateMachine | State machine definitions | -| **[Webhook](/docs/references/automation/webhook)** | `webhook.zod.ts` | Webhook, WebhookReceiver | Outbound webhooks | +| **[Webhook](/docs/references/automation/webhook)** | `webhook.zod.ts` | Webhook | Outbound webhooks | | **[ETL](/docs/references/automation/etl)** | `etl.zod.ts` | ETLPipeline | Data transformation pipelines | | **[Trigger Registry](/docs/references/automation/trigger-registry)** | `trigger-registry.zod.ts` | TriggerRegistry | Event-driven triggers | | **[Sync](/docs/references/automation/sync)** | `sync.zod.ts` | DataSyncConfig, SyncMode | Bi-directional data sync | diff --git a/content/docs/protocol/kernel/i18n-standard.mdx b/content/docs/protocol/kernel/i18n-standard.mdx index f551da7357..ca7a3e5efc 100644 --- a/content/docs/protocol/kernel/i18n-standard.mdx +++ b/content/docs/protocol/kernel/i18n-standard.mdx @@ -322,10 +322,8 @@ const label = i18n.t('account.fields.unknown', 'en'); **Design intent.** Automatic CLDR plural selection (the `key` / `key_plural` convention) is not yet implemented by the file adapter — `t()` performs only -`{{param}}` interpolation. To pluralize today, set `messageFormat: 'icu'` and -author ICU MessageFormat strings (see -[Use ICU MessageFormat](#4-use-icu-messageformat-for-complex-messages) below), -or select the form in application code. +`{{param}}` interpolation. To pluralize today, select the plural form in +application code before calling `t()`. ```typescript @@ -855,15 +853,7 @@ export default defineStack({ } ``` -### 4. Use ICU MessageFormat for Complex Messages - -```json -{ - "accountStatus": "{count, plural, =0 {No accounts} =1 {One account} other {# accounts}}" -} -``` - -### 5. Test All Locales +### 4. Test All Locales ```typescript // ✓ GOOD: Test with actual locales @@ -891,18 +881,6 @@ export default defineStack({ // Fallback chain fallbackLocale: 'en', - // File organization: 'bundled' | 'per_locale' | 'per_namespace' (default 'per_locale') - fileOrganization: 'per_locale', - - // Message format: 'simple' {variable} interpolation (default) | 'icu' MessageFormat - messageFormat: 'simple', - - // Load translations on demand? (default false) - lazyLoad: true, - - // Cache translations? (default true) - cache: true, - // Translation service — design intent, not yet a recognized config key // translationService: { // provider: 'google-translate', diff --git a/content/docs/references/automation/webhook.mdx b/content/docs/references/automation/webhook.mdx index 1f0a5fe244..a9400704af 100644 --- a/content/docs/references/automation/webhook.mdx +++ b/content/docs/references/automation/webhook.mdx @@ -46,8 +46,8 @@ value that silently never fires. ## TypeScript Usage ```typescript -import { Webhook, WebhookReceiver, WebhookTriggerType } from '@objectstack/spec/automation'; -import type { Webhook, WebhookReceiver, WebhookTriggerType } from '@objectstack/spec/automation'; +import { Webhook, WebhookTriggerType } from '@objectstack/spec/automation'; +import type { Webhook, WebhookTriggerType } from '@objectstack/spec/automation'; // Validate data const result = Webhook.parse(data); @@ -68,32 +68,10 @@ const result = Webhook.parse(data); | **url** | `string` | ✅ | External webhook endpoint URL | | **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE'>` | ✅ | HTTP method | | **headers** | `Record` | optional | Custom HTTP headers | -| **body** | `any` | optional | Request body payload (if not using default record data) | -| **payloadFields** | `string[]` | optional | Fields to include. Empty = All | -| **includeSession** | `boolean` | ✅ | Include user session info | -| **authentication** | `{ type: Enum<'none' \| 'bearer' \| 'basic' \| 'api-key'>; credentials?: Record }` | optional | Authentication configuration. [EXPERIMENTAL — not enforced] The webhook delivery path only applies HMAC signing via `secret`; bearer/basic/api-key credentials are not attached to outbound requests yet (liveness audit #1878/#1893). | -| **retryPolicy** | `{ maxRetries: integer; backoffStrategy: Enum<'exponential' \| 'linear' \| 'fixed'>; initialDelayMs: integer; maxDelayMs: integer }` | optional | Retry policy configuration | | **timeoutMs** | `integer` | ✅ | Request timeout in milliseconds | | **secret** | `string` | optional | Signing secret for HMAC signature verification | | **isActive** | `boolean` | ✅ | Whether webhook is active | | **description** | `string` | optional | Webhook description | -| **tags** | `string[]` | optional | Tags for organization | - - ---- - -## WebhookReceiver - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **name** | `string` | ✅ | Webhook receiver unique name (lowercase snake_case) | -| **path** | `string` | ✅ | URL Path (e.g. /webhooks/stripe) | -| **verificationType** | `Enum<'none' \| 'header_token' \| 'hmac' \| 'ip_whitelist'>` | ✅ | | -| **verificationParams** | `{ header?: string; secret?: string; ips?: string[] }` | optional | | -| **action** | `Enum<'trigger_flow' \| 'script' \| 'upsert_record'>` | ✅ | | -| **target** | `string` | ✅ | Flow ID or Script name | --- diff --git a/content/docs/references/integration/connector.mdx b/content/docs/references/integration/connector.mdx index a92fa3c1f3..aa9cb44cc8 100644 --- a/content/docs/references/integration/connector.mdx +++ b/content/docs/references/integration/connector.mdx @@ -470,16 +470,10 @@ Synchronization strategy | **url** | `string` | ✅ | External webhook endpoint URL | | **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE'>` | ✅ | HTTP method | | **headers** | `Record` | optional | Custom HTTP headers | -| **body** | `any` | optional | Request body payload (if not using default record data) | -| **payloadFields** | `string[]` | optional | Fields to include. Empty = All | -| **includeSession** | `boolean` | ✅ | Include user session info | -| **authentication** | `{ type: Enum<'none' \| 'bearer' \| 'basic' \| 'api-key'>; credentials?: Record }` | optional | Authentication configuration. [EXPERIMENTAL — not enforced] The webhook delivery path only applies HMAC signing via `secret`; bearer/basic/api-key credentials are not attached to outbound requests yet (liveness audit #1878/#1893). | -| **retryPolicy** | `{ maxRetries: integer; backoffStrategy: Enum<'exponential' \| 'linear' \| 'fixed'>; initialDelayMs: integer; maxDelayMs: integer }` | optional | Retry policy configuration | | **timeoutMs** | `integer` | ✅ | Request timeout in milliseconds | | **secret** | `string` | optional | Signing secret for HMAC signature verification | | **isActive** | `boolean` | ✅ | Whether webhook is active | | **description** | `string` | optional | Webhook description | -| **tags** | `string[]` | optional | Tags for organization | | **events** | `Enum<'record.created' \| 'record.updated' \| 'record.deleted' \| 'sync.started' \| 'sync.completed' \| 'sync.failed' \| 'auth.expired' \| 'rate_limit.exceeded'>[]` | optional | Connector events to subscribe to (not yet enforced — no runtime dispatches these; see #3197) | | **signatureAlgorithm** | `Enum<'hmac_sha256' \| 'hmac_sha512' \| 'none'>` | ✅ | Webhook signature algorithm | diff --git a/content/docs/references/integration/meta.json b/content/docs/references/integration/meta.json index 4ccb122e5a..d672bdfa95 100644 --- a/content/docs/references/integration/meta.json +++ b/content/docs/references/integration/meta.json @@ -5,7 +5,6 @@ "connector", "connector-auth", "mapping", - "translation", "---Transport & Storage---", "http", "message-queue", diff --git a/content/docs/references/integration/misc.mdx b/content/docs/references/integration/misc.mdx index 0342edc1ae..cdc46335fc 100644 --- a/content/docs/references/integration/misc.mdx +++ b/content/docs/references/integration/misc.mdx @@ -12,8 +12,8 @@ description: Misc protocol schemas ## TypeScript Usage ```typescript -import { AckMode, ApiVersionConfig, BuildConfig, CdcConfig, DatabaseConnector, DatabasePoolConfig, DatabaseTable, DeliveryGuarantee, DeploymentConfig, DlqConfig, DomainConfig, EdgeFunctionConfig, EnvironmentVariables, FileAccessPattern, FileFilterConfig, FileMetadataConfig, FileStorageConnector, FileStorageProvider, FileVersioningConfig, GitHubActionsWorkflow, GitHubCommitConfig, GitHubConnector, GitHubIssueTracking, GitHubProvider, GitHubPullRequestConfig, GitHubReleaseConfig, GitHubRepository, GitRepositoryConfig, MessageQueueConnector, ProducerConfig, SaasConnector, SaasObjectType, SaasProvider, SslConfig, StorageBucket, TopicQueue, VercelConnector, VercelFramework, VercelMonitoring, VercelProject, VercelProvider, VercelTeam } from '@objectstack/spec/integration'; -import type { AckMode, ApiVersionConfig, BuildConfig, CdcConfig, DatabaseConnector, DatabasePoolConfig, DatabaseTable, DeliveryGuarantee, DeploymentConfig, DlqConfig, DomainConfig, EdgeFunctionConfig, EnvironmentVariables, FileAccessPattern, FileFilterConfig, FileMetadataConfig, FileStorageConnector, FileStorageProvider, FileVersioningConfig, GitHubActionsWorkflow, GitHubCommitConfig, GitHubConnector, GitHubIssueTracking, GitHubProvider, GitHubPullRequestConfig, GitHubReleaseConfig, GitHubRepository, GitRepositoryConfig, MessageQueueConnector, ProducerConfig, SaasConnector, SaasObjectType, SaasProvider, SslConfig, StorageBucket, TopicQueue, VercelConnector, VercelFramework, VercelMonitoring, VercelProject, VercelProvider, VercelTeam } from '@objectstack/spec/integration'; +import { AckMode, ApiVersionConfig, BuildConfig, CdcConfig, DatabaseConnector, DatabasePoolConfig, DatabaseTable, DeliveryGuarantee, DeploymentConfig, DlqConfig, DomainConfig, EdgeFunctionConfig, EnvironmentVariables, FileAccessPattern, FileFilterConfig, FileMetadataConfig, FileStorageConnector, FileStorageProvider, FileVersioningConfig, GitHubActionsWorkflow, GitHubCommitConfig, GitHubConnector, GitHubIssueTracking, GitHubProvider, GitHubPullRequestConfig, GitHubReleaseConfig, GitHubRepository, GitRepositoryConfig, MessageFormat, MessageQueueConnector, ProducerConfig, SaasConnector, SaasObjectType, SaasProvider, SslConfig, StorageBucket, TopicQueue, VercelConnector, VercelFramework, VercelMonitoring, VercelProject, VercelProvider, VercelTeam } from '@objectstack/spec/integration'; +import type { AckMode, ApiVersionConfig, BuildConfig, CdcConfig, DatabaseConnector, DatabasePoolConfig, DatabaseTable, DeliveryGuarantee, DeploymentConfig, DlqConfig, DomainConfig, EdgeFunctionConfig, EnvironmentVariables, FileAccessPattern, FileFilterConfig, FileMetadataConfig, FileStorageConnector, FileStorageProvider, FileVersioningConfig, GitHubActionsWorkflow, GitHubCommitConfig, GitHubConnector, GitHubIssueTracking, GitHubProvider, GitHubPullRequestConfig, GitHubReleaseConfig, GitHubRepository, GitRepositoryConfig, MessageFormat, MessageQueueConnector, ProducerConfig, SaasConnector, SaasObjectType, SaasProvider, SslConfig, StorageBucket, TopicQueue, VercelConnector, VercelFramework, VercelMonitoring, VercelProject, VercelProvider, VercelTeam } from '@objectstack/spec/integration'; // Validate data const result = AckMode.parse(data); @@ -527,6 +527,22 @@ GitHub provider type | **autoDeployPreview** | `boolean` | ✅ | Auto-deploy preview branches | +--- + +## MessageFormat + +Message format/serialization + +### Allowed Values + +* `json` +* `xml` +* `protobuf` +* `avro` +* `text` +* `binary` + + --- ## MessageQueueConnector diff --git a/content/docs/references/integration/translation.mdx b/content/docs/references/integration/translation.mdx deleted file mode 100644 index 44014d67bd..0000000000 --- a/content/docs/references/integration/translation.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Translation -description: Translation protocol schemas ---- - -{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} - - -**Source:** `packages/spec/src/integration/translation.zod.ts` - - -## TypeScript Usage - -```typescript -import { MessageFormat } from '@objectstack/spec/integration'; -import type { MessageFormat } from '@objectstack/spec/integration'; - -// Validate data -const result = MessageFormat.parse(data); -``` - ---- - -## MessageFormat - -Message format/serialization - -### Allowed Values - -* `json` -* `xml` -* `protobuf` -* `avro` -* `text` -* `binary` - - ---- - diff --git a/content/docs/references/system/translation.mdx b/content/docs/references/system/translation.mdx index ec9ed8255a..0362693837 100644 --- a/content/docs/references/system/translation.mdx +++ b/content/docs/references/system/translation.mdx @@ -16,8 +16,8 @@ Translation data for a single field. ## TypeScript Usage ```typescript -import { ActionResultDialogTranslation, AppTranslationBundle, CoverageBreakdownEntry, FieldTranslation, Locale, MessageFormat, ObjectTranslationData, ObjectTranslationNode, TranslationBundle, TranslationConfig, TranslationCoverageResult, TranslationData, TranslationDiffItem, TranslationDiffStatus, TranslationFileOrganization } from '@objectstack/spec/system'; -import type { ActionResultDialogTranslation, AppTranslationBundle, CoverageBreakdownEntry, FieldTranslation, Locale, MessageFormat, ObjectTranslationData, ObjectTranslationNode, TranslationBundle, TranslationConfig, TranslationCoverageResult, TranslationData, TranslationDiffItem, TranslationDiffStatus, TranslationFileOrganization } from '@objectstack/spec/system'; +import { ActionResultDialogTranslation, AppTranslationBundle, CoverageBreakdownEntry, FieldTranslation, Locale, ObjectTranslationData, ObjectTranslationNode, TranslationBundle, TranslationConfig, TranslationCoverageResult, TranslationData, TranslationDiffItem, TranslationDiffStatus } from '@objectstack/spec/system'; +import type { ActionResultDialogTranslation, AppTranslationBundle, CoverageBreakdownEntry, FieldTranslation, Locale, ObjectTranslationData, ObjectTranslationNode, TranslationBundle, TranslationConfig, TranslationCoverageResult, TranslationData, TranslationDiffItem, TranslationDiffStatus } from '@objectstack/spec/system'; // Validate data const result = ActionResultDialogTranslation.parse(data); @@ -99,18 +99,6 @@ Translation data for a single field --- ---- - -## MessageFormat - -Message interpolation format: ICU MessageFormat or simple `{variable}` replacement - -### Allowed Values - -* `icu` -* `simple` - - --- ## ObjectTranslationData @@ -169,10 +157,6 @@ Internationalization configuration | **defaultLocale** | `string` | ✅ | Default locale (e.g., "en") | | **supportedLocales** | `string[]` | ✅ | Supported BCP-47 locale codes | | **fallbackLocale** | `string` | optional | Fallback locale code | -| **fileOrganization** | `Enum<'bundled' \| 'per_locale' \| 'per_namespace'>` | ✅ | File organization strategy | -| **messageFormat** | `Enum<'icu' \| 'simple'>` | ✅ | Message interpolation format (ICU MessageFormat or simple). [EXPERIMENTAL — 'icu' not enforced] No ICU MessageFormat engine is wired; messageFormat:'icu' is accepted but interpolation falls back to simple substitution (liveness audit #1878/#1893). | -| **lazyLoad** | `boolean` | ✅ | Load translations on demand | -| **cache** | `boolean` | ✅ | Cache loaded translations. [EXPERIMENTAL — not enforced] No runtime consumer reads this cache flag yet (liveness audit #1878/#1893). | --- @@ -252,16 +236,3 @@ Translation diff status: missing from bundle, redundant (no matching metadata), --- -## TranslationFileOrganization - -Translation file organization strategy - -### Allowed Values - -* `bundled` -* `per_locale` -* `per_namespace` - - ---- - diff --git a/content/docs/references/ui/responsive.mdx b/content/docs/references/ui/responsive.mdx index e3ef904700..935b588fc7 100644 --- a/content/docs/references/ui/responsive.mdx +++ b/content/docs/references/ui/responsive.mdx @@ -7,7 +7,7 @@ description: Responsive protocol schemas Breakpoint Name Enum -Matches the breakpoint names defined in theme.zod.ts BreakpointsSchema. +Standard Tailwind-style breakpoint names (xs–2xl). **Source:** `packages/spec/src/ui/responsive.zod.ts` diff --git a/content/docs/references/ui/theme.mdx b/content/docs/references/ui/theme.mdx index 9104025b11..9de36f3a85 100644 --- a/content/docs/references/ui/theme.mdx +++ b/content/docs/references/ui/theme.mdx @@ -16,8 +16,8 @@ Defines brand colors and their variants. ## TypeScript Usage ```typescript -import { Animation, BorderRadius, Breakpoints, ColorPalette, DensityMode, Shadow, Spacing, Theme, ThemeMode, Typography, WcagContrastLevel, ZIndex } from '@objectstack/spec/ui'; -import type { Animation, BorderRadius, Breakpoints, ColorPalette, DensityMode, Shadow, Spacing, Theme, ThemeMode, Typography, WcagContrastLevel, ZIndex } from '@objectstack/spec/ui'; +import { Animation, BorderRadius, ColorPalette, Shadow, Theme, ThemeMode, Typography, ZIndex } from '@objectstack/spec/ui'; +import type { Animation, BorderRadius, ColorPalette, Shadow, Theme, ThemeMode, Typography, ZIndex } from '@objectstack/spec/ui'; // Validate data const result = Animation.parse(data); @@ -53,22 +53,6 @@ const result = Animation.parse(data); | **full** | `string` | optional | Full border radius (50%) | ---- - -## Breakpoints - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **xs** | `string` | optional | Extra small breakpoint (e.g., 480px) | -| **sm** | `string` | optional | Small breakpoint (e.g., 640px) | -| **md** | `string` | optional | Medium breakpoint (e.g., 768px) | -| **lg** | `string` | optional | Large breakpoint (e.g., 1024px) | -| **xl** | `string` | optional | Extra large breakpoint (e.g., 1280px) | -| **2xl** | `string` | optional | 2X large breakpoint (e.g., 1536px) | - - --- ## ColorPalette @@ -96,17 +80,6 @@ const result = Animation.parse(data); | **secondaryDark** | `string` | optional | Darker shade of secondary | ---- - -## DensityMode - -### Allowed Values - -* `compact` -* `regular` -* `spacious` - - --- ## Shadow @@ -125,29 +98,6 @@ const result = Animation.parse(data); | **inner** | `string` | optional | Inner shadow (inset) | ---- - -## Spacing - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **0** | `string` | optional | 0 spacing (0) | -| **1** | `string` | optional | Spacing unit 1 (e.g., 0.25rem) | -| **2** | `string` | optional | Spacing unit 2 (e.g., 0.5rem) | -| **3** | `string` | optional | Spacing unit 3 (e.g., 0.75rem) | -| **4** | `string` | optional | Spacing unit 4 (e.g., 1rem) | -| **5** | `string` | optional | Spacing unit 5 (e.g., 1.25rem) | -| **6** | `string` | optional | Spacing unit 6 (e.g., 1.5rem) | -| **8** | `string` | optional | Spacing unit 8 (e.g., 2rem) | -| **10** | `string` | optional | Spacing unit 10 (e.g., 2.5rem) | -| **12** | `string` | optional | Spacing unit 12 (e.g., 3rem) | -| **16** | `string` | optional | Spacing unit 16 (e.g., 4rem) | -| **20** | `string` | optional | Spacing unit 20 (e.g., 5rem) | -| **24** | `string` | optional | Spacing unit 24 (e.g., 6rem) | - - --- ## Theme @@ -162,20 +112,12 @@ const result = Animation.parse(data); | **mode** | `Enum<'light' \| 'dark' \| 'auto'>` | ✅ | Theme mode (light, dark, or auto) | | **colors** | `{ primary: string; secondary?: string; accent?: string; success?: string; … }` | ✅ | Color palette configuration | | **typography** | `{ fontFamily?: object; fontSize?: object; fontWeight?: object; lineHeight?: object; … }` | optional | Typography settings | -| **spacing** | `{ 0?: string; 1?: string; 2?: string; 3?: string; … }` | optional | Spacing scale. [EXPERIMENTAL — not enforced] generateThemeVars does not emit spacing tokens yet (liveness audit #1878/#1893). | | **borderRadius** | `{ none?: string; sm?: string; base?: string; md?: string; … }` | optional | Border radius scale | | **shadows** | `{ none?: string; sm?: string; base?: string; md?: string; … }` | optional | Box shadow effects | -| **breakpoints** | `{ xs?: string; sm?: string; md?: string; lg?: string; … }` | optional | Responsive breakpoints. [EXPERIMENTAL — not enforced] generateThemeVars does not emit breakpoint tokens yet (liveness audit #1878/#1893). | | **animation** | `{ duration?: object; timing?: object }` | optional | Animation settings | | **zIndex** | `{ base?: number; dropdown?: number; sticky?: number; fixed?: number; … }` | optional | Z-index scale for layering | | **customVars** | `Record` | optional | Custom CSS variables (key-value pairs) | -| **logo** | `{ light?: string; dark?: string; favicon?: string }` | optional | Logo assets | | **extends** | `string` | optional | Base theme to extend from | -| **density** | `Enum<'compact' \| 'regular' \| 'spacious'>` | optional | Display density: compact, regular, or spacious. [EXPERIMENTAL — not enforced] The theme engine does not consume theme-level density yet; view-level densityMode is a separate live setting (liveness audit #1878/#1893). | -| **wcagContrast** | `Enum<'AA' \| 'AAA'>` | optional | WCAG color contrast level (AA or AAA) | -| **rtl** | `boolean` | optional | Enable right-to-left layout direction. [EXPERIMENTAL — not enforced] No renderer reads theme.rtl yet (liveness audit #1878/#1893). | -| **touchTarget** | `{ minWidth: number; minHeight: number; padding?: number; hitSlop?: object }` | optional | Touch target sizing defaults. [EXPERIMENTAL — not enforced] No renderer consumes touchTarget yet (liveness audit #1878/#1893). | -| **keyboardNavigation** | `{ tabOrder: Enum<'auto' \| 'manual'>; skipLinks: boolean; focusVisible: boolean; focusTrap?: object; … }` | optional | Keyboard focus management settings | --- @@ -204,16 +146,6 @@ const result = Animation.parse(data); | **letterSpacing** | `{ tighter?: string; tight?: string; normal?: string; wide?: string; … }` | optional | | ---- - -## WcagContrastLevel - -### Allowed Values - -* `AA` -* `AAA` - - --- ## ZIndex diff --git a/content/docs/ui/translations.mdx b/content/docs/ui/translations.mdx index 33468b3a9e..6e92c28636 100644 --- a/content/docs/ui/translations.mdx +++ b/content/docs/ui/translations.mdx @@ -112,13 +112,15 @@ metadata without doing lookups themselves. ## Organizing the files -`i18n.fileOrganization` picks the layout: +How you lay out translation source files is an authoring convention — your +import graph assembles whichever layout you choose into the bundles you +register. Common layouts: -- **`per_locale`** (default) — one file per language, combined in the bundle. +- **per locale** — one file per language, combined in the bundle. This is what the Todo example does (`en`, `zh-CN`, `ja-JP`). -- **`bundled`** — every locale in one file, like the CRM example above. Fine +- **bundled** — every locale in one file, like the CRM example above. Fine for two locales; unwieldy past that. -- **`per_namespace`** — split by module. +- **per namespace** — split by module. ## Draft with the CLI, gate in CI @@ -142,8 +144,8 @@ Honest limits worth knowing before you plan around them: - **`validationMessages` has no runtime consumer today.** The schema accepts it and the coverage tooling counts it, but nothing reads it back — validation errors are not translated through bundles yet. -- **`messageFormat: 'icu'` is declared but not implemented** — plural/gender - formatting isn't available; `simple` is what runs. +- **No ICU MessageFormat** — plural/gender formatting isn't available; + interpolation is always simple `{variable}` substitution. - **Two bundle shapes exist.** File-authored bundles use the `objects.` shape documented here, which is what the resolver reads. The `translation` metadata type authored at runtime uses an object-first (`o.`) shape, diff --git a/examples/app-crm/objectstack.config.ts b/examples/app-crm/objectstack.config.ts index c62cb6dc6b..f5a471d82b 100644 --- a/examples/app-crm/objectstack.config.ts +++ b/examples/app-crm/objectstack.config.ts @@ -64,9 +64,6 @@ export default defineStack({ defaultLocale: 'en', supportedLocales: ['en', 'zh-CN'], fallbackLocale: 'en', - messageFormat: 'simple', - lazyLoad: false, - cache: true, }, // Data diff --git a/examples/app-showcase/objectstack.config.ts b/examples/app-showcase/objectstack.config.ts index 12da6e0336..eb04d70bb4 100644 --- a/examples/app-showcase/objectstack.config.ts +++ b/examples/app-showcase/objectstack.config.ts @@ -172,9 +172,6 @@ export default defineStack({ defaultLocale: 'en', supportedLocales: ['en', 'zh-CN'], fallbackLocale: 'en', - messageFormat: 'simple', - lazyLoad: false, - cache: true, }, // Data diff --git a/examples/app-showcase/src/automation/webhooks/index.ts b/examples/app-showcase/src/automation/webhooks/index.ts index 10c5b009c9..222686327a 100644 --- a/examples/app-showcase/src/automation/webhooks/index.ts +++ b/examples/app-showcase/src/automation/webhooks/index.ts @@ -3,8 +3,8 @@ import { defineWebhook } from '@objectstack/spec/automation'; /** - * Outbound webhook — fans out task changes to an external endpoint with a - * retry policy. Authored via `defineStack({ webhooks })`; on boot the webhooks + * Outbound webhook — fans out task changes to an external endpoint. + * Authored via `defineStack({ webhooks })`; on boot the webhooks * plugin materializes this into a `sys_webhook` row (#3461) that the * auto-enqueuer dispatches off. * @@ -21,7 +21,6 @@ export const TaskChangedWebhook = defineWebhook({ triggers: ['create', 'update', 'delete'], url: 'https://hooks.example/showcase/task', method: 'POST', - retryPolicy: { maxRetries: 3, backoffStrategy: 'exponential', initialDelayMs: 1000, maxDelayMs: 30000 }, isActive: false, description: 'Sends task lifecycle events to an external system. Activate in Setup and point at a real endpoint.', }); diff --git a/examples/app-todo/objectstack.config.ts b/examples/app-todo/objectstack.config.ts index 66d0c57b99..a3e9d9e962 100644 --- a/examples/app-todo/objectstack.config.ts +++ b/examples/app-todo/objectstack.config.ts @@ -56,7 +56,6 @@ export default defineStack({ defaultLocale: 'en', supportedLocales: ['en', 'zh-CN', 'ja-JP'], fallbackLocale: 'en', - fileOrganization: 'per_locale', }, // I18n Translation Bundles (en, zh-CN, ja-JP) diff --git a/packages/cli/src/utils/lint-liveness-properties.test.ts b/packages/cli/src/utils/lint-liveness-properties.test.ts index 4fb2a32caa..34dbbbeecc 100644 --- a/packages/cli/src/utils/lint-liveness-properties.test.ts +++ b/packages/cli/src/utils/lint-liveness-properties.test.ts @@ -177,9 +177,11 @@ describe('lintLivenessProperties', () => { }); it('emits exactly one warning for a fully-authored (showcase-shaped) webhook', () => { - // object/triggers/method/retryPolicy/isActive/description are all dead too, - // but only `url` carries authorWarn — so the whole no-op artifact yields ONE - // finding, not one-per-prop. (isActive is default(true), deliberately unmarked.) + // object/triggers/method/isActive/description are dead too, but only `url` + // carries authorWarn — so the whole no-op artifact yields ONE finding, not + // one-per-prop. (isActive is default(true), deliberately unmarked. + // #3494 pruned the aspirational body/payloadFields/includeSession/ + // authentication/retryPolicy/tags props outright.) const findings = lintLivenessProperties({ webhooks: [{ name: 'showcase_task_changed', @@ -187,7 +189,6 @@ describe('lintLivenessProperties', () => { triggers: ['create', 'update', 'delete'], url: 'https://hooks.example/showcase/task', method: 'POST', - retryPolicy: { maxRetries: 3, backoffStrategy: 'exponential' }, isActive: true, description: 'Sends task lifecycle events to an external system.', }], @@ -196,13 +197,6 @@ describe('lintLivenessProperties', () => { expect(findings[0].message).toContain('`url`'); }); - it('also warns on authentication (experimental — HMAC-secret-only)', () => { - const findings = lintLivenessProperties({ - webhooks: [{ name: 'w1', url: 'https://hooks.example/x', authentication: { type: 'bearer' } }], - }); - expect(paths(findings).some((m) => m.includes('`authentication`'))).toBe(true); - }); - it('does NOT warn on isActive (default(true) boolean, deliberately unmarked)', () => { const findings = lintLivenessProperties({ webhooks: [{ name: 'w1', url: 'https://hooks.example/x', isActive: true }], diff --git a/packages/spec/api-surface.json b/packages/spec/api-surface.json index 5b7557a6b8..9b985636d1 100644 --- a/packages/spec/api-surface.json +++ b/packages/spec/api-surface.json @@ -881,8 +881,6 @@ "METADATA_FORM_REGISTRY (const)", "MaskingVisibilityRule (type)", "MaskingVisibilityRuleSchema (const)", - "MessageFormat (type)", - "MessageFormatSchema (const)", "MessageQueueConfig (type)", "MessageQueueConfigSchema (const)", "MessageQueueProvider (type)", @@ -1237,8 +1235,6 @@ "TranslationDiffItemSchema (const)", "TranslationDiffStatus (type)", "TranslationDiffStatusSchema (const)", - "TranslationFileOrganization (type)", - "TranslationFileOrganizationSchema (const)", "UserActivityStatus (type)", "VectorClock (type)", "VectorClockSchema (const)", @@ -2139,8 +2135,6 @@ "WaitTimeoutBehaviorSchema (const)", "Webhook (type)", "WebhookInput (type)", - "WebhookReceiver (type)", - "WebhookReceiverSchema (const)", "WebhookSchema (const)", "WebhookTriggerType (const)", "analyzeRegion (function)", @@ -3119,8 +3113,6 @@ "BreakpointColumnMapSchema (const)", "BreakpointName (type)", "BreakpointOrderMapSchema (const)", - "Breakpoints (type)", - "BreakpointsSchema (const)", "CalendarConfigSchema (const)", "ChartAnnotation (type)", "ChartAnnotationSchema (const)", @@ -3169,8 +3161,6 @@ "DatasetSchema (const)", "DateFormat (type)", "DateFormatSchema (const)", - "DensityMode (type)", - "DensityModeSchema (const)", "DerivedMeasureOp (const)", "DerivedMeasureOpValue (type)", "DndConfig (type)", @@ -3364,8 +3354,6 @@ "ShadowSchema (const)", "SharingConfig (type)", "SharingConfigSchema (const)", - "Spacing (type)", - "SpacingSchema (const)", "StyleMap (type)", "StyleMapSchema (const)", "SwipeDirection (type)", @@ -3425,8 +3413,6 @@ "ViewTabSchema (const)", "VisualizationType (type)", "VisualizationTypeSchema (const)", - "WcagContrastLevel (type)", - "WcagContrastLevelSchema (const)", "WidgetActionType (type)", "WidgetActionTypeSchema (const)", "WidgetColorVariant (type)", diff --git a/packages/spec/json-schema.manifest.json b/packages/spec/json-schema.manifest.json index 4e39f46797..d3606e42a0 100644 --- a/packages/spec/json-schema.manifest.json +++ b/packages/spec/json-schema.manifest.json @@ -584,7 +584,6 @@ "automation/WaitResumePayload", "automation/WaitTimeoutBehavior", "automation/Webhook", - "automation/WebhookReceiver", "automation/WebhookTriggerType", "cloud/AnalyticsTimeRange", "cloud/AppDiscoveryRequest", @@ -1430,7 +1429,6 @@ "system/LoggerConfig", "system/LoggingConfig", "system/MaskingVisibilityRule", - "system/MessageFormat", "system/MessageQueueConfig", "system/MessageQueueProvider", "system/MetadataCollectionInfo", @@ -1597,7 +1595,6 @@ "system/TranslationData", "system/TranslationDiffItem", "system/TranslationDiffStatus", - "system/TranslationFileOrganization", "system/UserActivityStatus", "system/VectorClock", "system/WorkerStats", @@ -1620,7 +1617,6 @@ "ui/BreakpointColumnMap", "ui/BreakpointName", "ui/BreakpointOrderMap", - "ui/Breakpoints", "ui/CalendarConfig", "ui/ChartAnnotation", "ui/ChartAxis", @@ -1642,7 +1638,6 @@ "ui/DatasetDimension", "ui/DatasetMeasure", "ui/DateFormat", - "ui/DensityMode", "ui/DerivedMeasureOp", "ui/DndConfig", "ui/DragConstraint", @@ -1747,7 +1742,6 @@ "ui/SelectionConfig", "ui/Shadow", "ui/SharingConfig", - "ui/Spacing", "ui/StyleMap", "ui/SwipeDirection", "ui/SwipeGestureConfig", @@ -1775,7 +1769,6 @@ "ui/ViewSharing", "ui/ViewTab", "ui/VisualizationType", - "ui/WcagContrastLevel", "ui/WidgetActionType", "ui/WidgetColorVariant", "ui/WidgetEvent", diff --git a/packages/spec/liveness/webhook.json b/packages/spec/liveness/webhook.json index fe50038c34..582c47a1c2 100644 --- a/packages/spec/liveness/webhook.json +++ b/packages/spec/liveness/webhook.json @@ -32,27 +32,6 @@ "status": "dead", "note": "Authored value never materialized. No first-class sys_webhook column — the dispatcher reads headers only from the `definition_json` textarea of a hand-created row (auto-enqueuer.ts:275). A materializer would fold this into definition_json." }, - "body": { - "status": "dead", - "note": "No sink anywhere. The dispatcher always sends its own fixed envelope (object/recordId/action/timestamp + full record, auto-enqueuer.ts:336-342); a custom body is never read. Vestigial even for a future materializer." - }, - "payloadFields": { - "status": "dead", - "note": "No sink anywhere. No field projection — the full record is always sent (auto-enqueuer.ts:341). Never read." - }, - "includeSession": { - "status": "dead", - "note": "No sink anywhere. No session is injected into the enqueue payload. Never read." - }, - "authentication": { - "status": "experimental", - "authorHint": "Only HMAC signing via `secret` is applied to deliveries; bearer/basic/api-key credentials are never attached to outbound requests (liveness audit #1878/#1893).", - "note": "Self-marked [EXPERIMENTAL — not enforced] on the schema (webhook.zod.ts:102); pinned here so the compile lint warns on it (marker-only classifications aren't read by the lint). At runtime the delivery path applies HMAC via `secret` only (auto-enqueuer.ts:276) — bearer/basic/api-key are inert." - }, - "retryPolicy": { - "status": "dead", - "note": "No sink anywhere. Not present in EnqueueHttpInput (service-messaging/src/http-outbox.ts:87-98); the enqueuer passes no retry fields and the messaging HttpDispatcher owns a fixed retry schedule. Authored maxRetries/backoffStrategy/initialDelayMs/maxDelayMs are all ignored. (The showcase's TaskChangedWebhook authors a retryPolicy that does nothing — the canonical no-op.)" - }, "timeoutMs": { "status": "dead", "note": "Authored value never materialized. Honored only from the `definition_json` textarea of a hand-created row (auto-enqueuer.ts:277 → http-outbox.ts:96), never from this top-level prop." @@ -69,10 +48,6 @@ "description": { "status": "dead", "note": "Authored value never materialized. Runtime col sys-webhook.object.ts:160 (Studio-editable; inert in dispatch). Salvageable 1:1." - }, - "tags": { - "status": "dead", - "note": "No sink anywhere. No sys_webhook column, never read. Vestigial." } } } diff --git a/packages/spec/src/automation/webhook.test.ts b/packages/spec/src/automation/webhook.test.ts index c5aa34a5a9..086dfa3526 100644 --- a/packages/spec/src/automation/webhook.test.ts +++ b/packages/spec/src/automation/webhook.test.ts @@ -1,10 +1,8 @@ import { describe, it, expect } from 'vitest'; import { WebhookSchema, - WebhookReceiverSchema, WebhookTriggerType, type Webhook, - type WebhookReceiver, } from './webhook.zod'; describe('WebhookTriggerType', () => { @@ -73,7 +71,6 @@ describe('WebhookSchema', () => { }); expect(webhook.method).toBe('POST'); - expect(webhook.includeSession).toBe(false); expect(webhook.isActive).toBe(true); expect(webhook.timeoutMs).toBe(30000); }); @@ -91,9 +88,6 @@ describe('WebhookSchema', () => { 'Authorization': 'Bearer token123', 'X-Custom-Header': 'value', }, - payloadFields: ['name', 'email', 'phone'], - includeSession: true, - retryCount: 5, isActive: true, }); @@ -164,43 +158,6 @@ describe('WebhookSchema', () => { expect(webhook.headers).toHaveProperty('X-API-Key'); }); - it('should accept payload field filtering', () => { - const webhook = WebhookSchema.parse({ - name: 'filtered_webhook', - object: 'contact', - triggers: ['create'], - url: 'https://example.com/webhook', - payloadFields: ['email', 'name'], - }); - - expect(webhook.payloadFields).toEqual(['email', 'name']); - }); - - it('should accept session inclusion', () => { - const webhook = WebhookSchema.parse({ - name: 'session_webhook', - object: 'account', - triggers: ['create'], - url: 'https://example.com/webhook', - includeSession: true, - }); - - expect(webhook.includeSession).toBe(true); - }); - - it('should accept custom retry count', () => { - const webhook = WebhookSchema.parse({ - name: 'retry_webhook', - url: 'https://example.com/webhook', - retryPolicy: { - maxRetries: 10, - backoffStrategy: 'linear', - } - }); - - expect(webhook.retryPolicy?.maxRetries).toBe(10); - }); - it('should accept inactive webhook', () => { const webhook = WebhookSchema.parse({ name: 'inactive_webhook', @@ -237,7 +194,6 @@ describe('WebhookSchema', () => { triggers: ['create'], url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXX', method: 'POST', - payloadFields: ['name', 'amount', 'stage'], }); expect(webhook.url).toContain('slack.com'); @@ -250,7 +206,6 @@ describe('WebhookSchema', () => { triggers: ['create', 'update'], url: 'https://example.com/stripe/webhook', secret: 'whsec_stripe_signing_secret', - retryCount: 5, }); expect(webhook.secret).toContain('whsec'); @@ -266,177 +221,3 @@ describe('WebhookSchema', () => { })).toThrow(); }); }); - -describe('WebhookReceiverSchema', () => { - it('should accept valid minimal webhook receiver', () => { - const receiver: WebhookReceiver = { - name: 'stripe_receiver', - path: '/webhooks/stripe', - target: 'stripe_flow', - }; - - expect(() => WebhookReceiverSchema.parse(receiver)).not.toThrow(); - }); - - it('should validate receiver name format (snake_case)', () => { - expect(() => WebhookReceiverSchema.parse({ - name: 'valid_receiver_name', - path: '/webhooks/test', - target: 'flow_id', - })).not.toThrow(); - - expect(() => WebhookReceiverSchema.parse({ - name: 'InvalidReceiver', - path: '/webhooks/test', - target: 'flow_id', - })).toThrow(); - }); - - it('should apply default values', () => { - const receiver = WebhookReceiverSchema.parse({ - name: 'test_receiver', - path: '/webhooks/test', - target: 'flow_id', - }); - - expect(receiver.verificationType).toBe('none'); - expect(receiver.action).toBe('trigger_flow'); - }); - - it('should accept receiver with all fields', () => { - const receiver = WebhookReceiverSchema.parse({ - name: 'secure_receiver', - path: '/webhooks/secure', - verificationType: 'hmac', - verificationParams: { - header: 'X-Hub-Signature', - secret: 'secret_key', - }, - action: 'trigger_flow', - target: 'processing_flow', - }); - - expect(receiver.verificationType).toBe('hmac'); - expect(receiver.verificationParams?.secret).toBe('secret_key'); - }); - - it('should accept different verification types', () => { - const types: Array = ['none', 'header_token', 'hmac', 'ip_whitelist']; - - types.forEach(type => { - const receiver = WebhookReceiverSchema.parse({ - name: 'test_receiver', - path: '/webhooks/test', - verificationType: type, - target: 'flow_id', - }); - expect(receiver.verificationType).toBe(type); - }); - }); - - it('should accept different action types', () => { - const actions: Array = ['trigger_flow', 'script', 'upsert_record']; - - actions.forEach(action => { - const receiver = WebhookReceiverSchema.parse({ - name: 'test_receiver', - path: '/webhooks/test', - action, - target: 'target_id', - }); - expect(receiver.action).toBe(action); - }); - }); - - it('should accept header token verification', () => { - const receiver = WebhookReceiverSchema.parse({ - name: 'header_auth_receiver', - path: '/webhooks/auth', - verificationType: 'header_token', - verificationParams: { - header: 'X-API-Token', - secret: 'expected_token_value', - }, - target: 'flow_id', - }); - - expect(receiver.verificationType).toBe('header_token'); - }); - - it('should accept HMAC verification', () => { - const receiver = WebhookReceiverSchema.parse({ - name: 'hmac_receiver', - path: '/webhooks/hmac', - verificationType: 'hmac', - verificationParams: { - header: 'X-Hub-Signature-256', - secret: 'hmac_secret', - }, - target: 'flow_id', - }); - - expect(receiver.verificationType).toBe('hmac'); - }); - - it('should accept IP whitelist verification', () => { - const receiver = WebhookReceiverSchema.parse({ - name: 'ip_receiver', - path: '/webhooks/ip', - verificationType: 'ip_whitelist', - verificationParams: { - ips: ['192.168.1.1', '10.0.0.0/8'], - }, - target: 'flow_id', - }); - - expect(receiver.verificationParams?.ips).toHaveLength(2); - }); - - it('should handle GitHub webhook receiver', () => { - const receiver = WebhookReceiverSchema.parse({ - name: 'github_webhook', - path: '/webhooks/github', - verificationType: 'hmac', - verificationParams: { - header: 'X-Hub-Signature-256', - secret: 'github_webhook_secret', - }, - action: 'trigger_flow', - target: 'github_flow', - }); - - expect(receiver.path).toBe('/webhooks/github'); - }); - - it('should handle Stripe webhook receiver', () => { - const receiver = WebhookReceiverSchema.parse({ - name: 'stripe_webhook', - path: '/webhooks/stripe', - verificationType: 'hmac', - verificationParams: { - secret: 'whsec_stripe_signing_secret', - }, - action: 'upsert_record', - target: 'payment_object', - }); - - expect(receiver.action).toBe('upsert_record'); - }); - - it('should reject receiver without required fields', () => { - expect(() => WebhookReceiverSchema.parse({ - path: '/webhooks/test', - target: 'flow_id', - })).toThrow(); - - expect(() => WebhookReceiverSchema.parse({ - name: 'test_receiver', - target: 'flow_id', - })).toThrow(); - - expect(() => WebhookReceiverSchema.parse({ - name: 'test_receiver', - path: '/webhooks/test', - })).toThrow(); - }); -}); diff --git a/packages/spec/src/automation/webhook.zod.ts b/packages/spec/src/automation/webhook.zod.ts index 55a1e63685..f343f06b68 100644 --- a/packages/spec/src/automation/webhook.zod.ts +++ b/packages/spec/src/automation/webhook.zod.ts @@ -73,16 +73,17 @@ export const WebhookTriggerType = z.enum([ * url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXX', * method: 'POST', * headers: { 'Content-Type': 'application/json' }, - * authentication: { - * type: 'bearer', - * credentials: { token: process.env.SLACK_TOKEN } - * }, - * retryPolicy: { - * maxRetries: 3, - * backoffStrategy: 'exponential' - * } + * secret: process.env.WEBHOOK_SIGNING_SECRET, * } * ``` + * + * #3494: the aspirational props `body`, `payloadFields`, `includeSession`, + * `authentication` and `tags` were removed — the delivery path always sends its + * own fixed envelope, only HMAC signing via `secret` is applied, and none of + * them had a sink anywhere (liveness audit #1878/#1893). `retryPolicy` was + * removed too: delivery retries are owned by the messaging outbox's fixed + * schedule, which never read the authored policy. The inbound + * `WebhookReceiverSchema` (never consumed by any runtime) was removed as well. */ export const WebhookSchema = lazySchema(() => z.object({ name: SnakeCaseIdentifierSchema.describe('Webhook unique name (lowercase snake_case)'), @@ -98,28 +99,7 @@ export const WebhookSchema = lazySchema(() => z.object({ /** Headers */ headers: z.record(z.string(), z.string()).optional().describe('Custom HTTP headers'), - - /** Body/Payload */ - body: z.unknown().optional().describe('Request body payload (if not using default record data)'), - - /** Payload Configuration */ - payloadFields: z.array(z.string()).optional().describe('Fields to include. Empty = All'), - includeSession: z.boolean().default(false).describe('Include user session info'), - - /** Authentication */ - authentication: z.object({ - type: z.enum(['none', 'bearer', 'basic', 'api-key']).describe('Authentication type'), - credentials: z.record(z.string(), z.string()).optional().describe('Authentication credentials'), - }).optional().describe("Authentication configuration. [EXPERIMENTAL — not enforced] The webhook delivery path only applies HMAC signing via `secret`; bearer/basic/api-key credentials are not attached to outbound requests yet (liveness audit #1878/#1893)."), - - /** Retry Policy */ - retryPolicy: z.object({ - maxRetries: z.number().int().min(0).max(10).default(3).describe('Maximum retry attempts'), - backoffStrategy: z.enum(['exponential', 'linear', 'fixed']).default('exponential').describe('Backoff strategy'), - initialDelayMs: z.number().int().min(100).default(1000).describe('Initial retry delay in milliseconds'), - maxDelayMs: z.number().int().min(1000).default(60000).describe('Maximum retry delay in milliseconds'), - }).optional().describe('Retry policy configuration'), - + /** Timeout */ timeoutMs: z.number().int().min(1000).max(300000).default(30000).describe('Request timeout in milliseconds'), @@ -131,45 +111,11 @@ export const WebhookSchema = lazySchema(() => z.object({ /** Metadata */ description: z.string().optional().describe('Webhook description'), - tags: z.array(z.string()).optional().describe('Tags for organization'), -})); - -/** - * Webhook Receiver Schema (Inbound) - * Handling incoming HTTP hooks from Stripe, Slack, etc. - * - * **NAMING CONVENTION:** - * Webhook receiver names are machine identifiers and must be lowercase snake_case. - * - * @example Good names - * - 'stripe_webhook_handler' - * - 'github_events' - * - 'twilio_status_callback' - * - * @example Bad names (will be rejected) - * - 'StripeWebhookHandler' (PascalCase) - */ -export const WebhookReceiverSchema = lazySchema(() => z.object({ - name: SnakeCaseIdentifierSchema.describe('Webhook receiver unique name (lowercase snake_case)'), - path: z.string().describe('URL Path (e.g. /webhooks/stripe)'), - - /** Verification */ - verificationType: z.enum(['none', 'header_token', 'hmac', 'ip_whitelist']).default('none'), - verificationParams: z.object({ - header: z.string().optional(), - secret: z.string().optional(), - ips: z.array(z.string()).optional() - }).optional(), - - /** Action */ - action: z.enum(['trigger_flow', 'script', 'upsert_record']).default('trigger_flow'), - target: z.string().describe('Flow ID or Script name'), })); export type Webhook = z.infer; /** Authoring input for {@link Webhook} — defaulted fields are optional. */ export type WebhookInput = z.input; -export type WebhookReceiver = z.infer; /** * Type-safe factory for an outbound webhook. Validates at authoring time via diff --git a/packages/spec/src/integration/connector.test.ts b/packages/spec/src/integration/connector.test.ts index 2452d7fb0e..a2ed3c5bbf 100644 --- a/packages/spec/src/integration/connector.test.ts +++ b/packages/spec/src/integration/connector.test.ts @@ -252,22 +252,6 @@ describe('WebhookConfigSchema', () => { expect(() => WebhookConfigSchema.parse(webhook)).not.toThrow(); }); - it('should accept webhook with retry configuration', () => { - const webhook = { - name: 'retry_webhook', - url: 'https://api.example.com/webhooks', - events: ['sync.completed'], - retryPolicy: { - maxRetries: 3, - backoffStrategy: 'exponential' as const, - initialDelayMs: 1000, - }, - }; - - const parsed = WebhookConfigSchema.parse(webhook); - expect(parsed.retryPolicy?.maxRetries).toBe(3); - }); - it('should use default values', () => { const webhook = { name: 'default_webhook', diff --git a/packages/spec/src/system/translation.test.ts b/packages/spec/src/system/translation.test.ts index 357ef88efa..9e9b9ddfae 100644 --- a/packages/spec/src/system/translation.test.ts +++ b/packages/spec/src/system/translation.test.ts @@ -5,9 +5,7 @@ import { LocaleSchema, FieldTranslationSchema, ObjectTranslationDataSchema, - TranslationFileOrganizationSchema, TranslationConfigSchema, - MessageFormatSchema, ObjectTranslationNodeSchema, AppTranslationBundleSchema, TranslationDiffStatusSchema, @@ -566,34 +564,12 @@ describe('ObjectTranslationDataSchema', () => { }); }); -// ============================================================================ -// TranslationFileOrganizationSchema -// ============================================================================ - -describe('TranslationFileOrganizationSchema', () => { - it('should accept bundled', () => { - expect(TranslationFileOrganizationSchema.parse('bundled')).toBe('bundled'); - }); - - it('should accept per_locale', () => { - expect(TranslationFileOrganizationSchema.parse('per_locale')).toBe('per_locale'); - }); - - it('should accept per_namespace', () => { - expect(TranslationFileOrganizationSchema.parse('per_namespace')).toBe('per_namespace'); - }); - - it('should reject invalid value', () => { - expect(() => TranslationFileOrganizationSchema.parse('flat')).toThrow(); - }); -}); - // ============================================================================ // TranslationConfigSchema // ============================================================================ describe('TranslationConfigSchema', () => { - it('should accept minimal config with defaults', () => { + it('should accept minimal config', () => { const config: TranslationConfig = TranslationConfigSchema.parse({ defaultLocale: 'en', supportedLocales: ['en'], @@ -601,9 +577,6 @@ describe('TranslationConfigSchema', () => { expect(config.defaultLocale).toBe('en'); expect(config.supportedLocales).toEqual(['en']); expect(config.fallbackLocale).toBeUndefined(); - expect(config.fileOrganization).toBe('per_locale'); - expect(config.lazyLoad).toBe(false); - expect(config.cache).toBe(true); }); it('should accept full multi-language config', () => { @@ -611,22 +584,9 @@ describe('TranslationConfigSchema', () => { defaultLocale: 'en', supportedLocales: ['en', 'zh-CN', 'ja-JP', 'es-ES'], fallbackLocale: 'en', - fileOrganization: 'per_namespace', - lazyLoad: true, - cache: true, }); expect(config.supportedLocales).toHaveLength(4); - expect(config.fileOrganization).toBe('per_namespace'); - expect(config.lazyLoad).toBe(true); - }); - - it('should accept bundled organization for small projects', () => { - const config = TranslationConfigSchema.parse({ - defaultLocale: 'en', - supportedLocales: ['en', 'zh-CN'], - fileOrganization: 'bundled', - }); - expect(config.fileOrganization).toBe('bundled'); + expect(config.fallbackLocale).toBe('en'); }); it('should reject config without defaultLocale', () => { @@ -644,38 +604,6 @@ describe('TranslationConfigSchema', () => { }), ).toThrow(); }); - - it('should default messageFormat to simple', () => { - const config = TranslationConfigSchema.parse({ - defaultLocale: 'en', - supportedLocales: ['en'], - }); - expect(config.messageFormat).toBe('simple'); - }); - - it('should accept ICU message format config', () => { - const config = TranslationConfigSchema.parse({ - defaultLocale: 'en', - supportedLocales: ['en', 'ar-SA'], - messageFormat: 'icu', - }); - expect(config.messageFormat).toBe('icu'); - }); -}); - -// ============================================================================ -// MessageFormatSchema -// ============================================================================ - -describe('MessageFormatSchema', () => { - it('should accept icu and simple', () => { - expect(MessageFormatSchema.parse('icu')).toBe('icu'); - expect(MessageFormatSchema.parse('simple')).toBe('simple'); - }); - - it('should reject invalid format', () => { - expect(() => MessageFormatSchema.parse('mf2')).toThrow(); - }); }); // ============================================================================ diff --git a/packages/spec/src/system/translation.zod.ts b/packages/spec/src/system/translation.zod.ts index 7d40307f33..27b311bbd1 100644 --- a/packages/spec/src/system/translation.zod.ts +++ b/packages/spec/src/system/translation.zod.ts @@ -347,53 +347,6 @@ export function defineTranslationBundle(config: z.input z.enum([ - 'bundled', - 'per_locale', - 'per_namespace', -]).describe('Translation file organization strategy')); - -export type TranslationFileOrganization = z.infer; - // ──────────────────────────────────────────────────────────────────────────── // Translation Configuration // ──────────────────────────────────────────────────────────────────────────── @@ -403,6 +356,11 @@ export type TranslationFileOrganization = z.infer z.enum([ - 'icu', - 'simple', -]).describe('Message interpolation format: ICU MessageFormat or simple {variable} replacement')); - -export type MessageFormat = z.infer; - export const TranslationConfigSchema = lazySchema(() => z.object({ /** Default locale for the application */ defaultLocale: LocaleSchema.describe('Default locale (e.g., "en")'), @@ -438,21 +380,6 @@ export const TranslationConfigSchema = lazySchema(() => z.object({ supportedLocales: z.array(LocaleSchema).describe('Supported BCP-47 locale codes'), /** Fallback locale when translation is not found */ fallbackLocale: LocaleSchema.optional().describe('Fallback locale code'), - /** How translation files are organized on disk */ - fileOrganization: TranslationFileOrganizationSchema.default('per_locale') - .describe('File organization strategy'), - /** - * Message interpolation format. - * When set to `'icu'`, messages and validationMessages are expected to use - * ICU MessageFormat syntax (plurals, select, number/date skeletons). - * @default 'simple' - */ - messageFormat: MessageFormatSchema.default('simple') - .describe("Message interpolation format (ICU MessageFormat or simple). [EXPERIMENTAL — 'icu' not enforced] No ICU MessageFormat engine is wired; messageFormat:'icu' is accepted but interpolation falls back to simple substitution (liveness audit #1878/#1893)."), - /** Load translations on demand instead of eagerly */ - lazyLoad: z.boolean().default(false).describe('Load translations on demand'), - /** Cache loaded translations in memory */ - cache: z.boolean().default(true).describe('Cache loaded translations. [EXPERIMENTAL — not enforced] No runtime consumer reads this cache flag yet (liveness audit #1878/#1893).'), }).describe('Internationalization configuration')); export type TranslationConfig = z.infer; diff --git a/packages/spec/src/ui/responsive.zod.ts b/packages/spec/src/ui/responsive.zod.ts index 09817700d4..25c9317328 100644 --- a/packages/spec/src/ui/responsive.zod.ts +++ b/packages/spec/src/ui/responsive.zod.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; /** * Breakpoint Name Enum - * Matches the breakpoint names defined in theme.zod.ts BreakpointsSchema. + * Standard Tailwind-style breakpoint names (xs–2xl). */ import { lazySchema } from '../shared/lazy-schema'; export const BreakpointName = z.enum(['xs', 'sm', 'md', 'lg', 'xl', '2xl']); @@ -17,8 +17,6 @@ export type BreakpointName = z.infer; * Provides responsive layout configuration for UI components. * Maps breakpoint names to layout behavior (columns, visibility, order). * - * Aligned with theme.zod.ts BreakpointsSchema for a unified responsive system. - * * @example * ```typescript * const config: ResponsiveConfig = { diff --git a/packages/spec/src/ui/theme.test.ts b/packages/spec/src/ui/theme.test.ts index 01542909d9..69910eee9d 100644 --- a/packages/spec/src/ui/theme.test.ts +++ b/packages/spec/src/ui/theme.test.ts @@ -3,15 +3,10 @@ import { ThemeSchema, ThemeMode, ThemeModeSchema, - DensityModeSchema, - WcagContrastLevelSchema, ColorPaletteSchema, TypographySchema, - SpacingSchema, BorderRadiusSchema, ShadowSchema, - DensityMode, - WcagContrastLevel, type Theme, type ColorPalette, } from './theme.zod'; @@ -125,20 +120,6 @@ describe('TypographySchema', () => { }); }); -describe('SpacingSchema', () => { - it('should accept spacing scale', () => { - const spacing = { - '0': '0', - '1': '0.25rem', - '2': '0.5rem', - '4': '1rem', - '8': '2rem', - }; - - expect(() => SpacingSchema.parse(spacing)).not.toThrow(); - }); -}); - describe('BorderRadiusSchema', () => { it('should accept border radius scale', () => { const borderRadius = { @@ -235,10 +216,6 @@ describe('ThemeSchema', () => { xl: '1.25rem', }, }, - spacing: { - '4': '1rem', - '8': '2rem', - }, borderRadius: { base: '0.25rem', lg: '0.5rem', @@ -252,23 +229,6 @@ describe('ThemeSchema', () => { expect(() => ThemeSchema.parse(theme)).not.toThrow(); }); - it('should accept theme with logo configuration', () => { - const theme: Theme = { - name: 'branded_theme', - label: 'Branded Theme', - colors: { - primary: '#007BFF', - }, - logo: { - light: '/assets/logo-light.svg', - dark: '/assets/logo-dark.svg', - favicon: '/assets/favicon.ico', - }, - }; - - expect(() => ThemeSchema.parse(theme)).not.toThrow(); - }); - it('should accept theme with custom CSS variables', () => { const theme: Theme = { name: 'custom_vars_theme', @@ -392,24 +352,6 @@ describe('ThemeSchema', () => { expect(() => ThemeSchema.parse(theme)).not.toThrow(); }); - it('should accept theme with breakpoints', () => { - const theme: Theme = { - name: 'responsive_theme', - label: 'Responsive Theme', - colors: { - primary: '#007BFF', - }, - breakpoints: { - sm: '640px', - md: '768px', - lg: '1024px', - xl: '1280px', - '2xl': '1536px', - }, - }; - - expect(() => ThemeSchema.parse(theme)).not.toThrow(); - }); }); describe('Real-World Theme Examples', () => { @@ -439,10 +381,6 @@ describe('Real-World Theme Examples', () => { heading: 'Poppins, sans-serif', }, }, - logo: { - light: '/assets/logo.svg', - favicon: '/assets/favicon.ico', - }, }; expect(() => ThemeSchema.parse(theme)).not.toThrow(); @@ -483,68 +421,6 @@ describe('Real-World Theme Examples', () => { }); }); -describe('DensityMode', () => { - it('should accept all density modes', () => { - const modes = ['compact', 'regular', 'spacious'] as const; - modes.forEach(mode => { - expect(() => DensityMode.parse(mode)).not.toThrow(); - }); - }); - it('should reject invalid density mode', () => { - expect(() => DensityMode.parse('tight')).toThrow(); - }); -}); - -describe('WcagContrastLevel', () => { - it('should accept AA and AAA', () => { - expect(() => WcagContrastLevel.parse('AA')).not.toThrow(); - expect(() => WcagContrastLevel.parse('AAA')).not.toThrow(); - }); - it('should reject invalid level', () => { - expect(() => WcagContrastLevel.parse('A')).toThrow(); - }); -}); - -describe('Theme Density, WCAG, and RTL', () => { - it('should accept theme with density mode', () => { - expect(() => ThemeSchema.parse({ - name: 'dense_theme', - label: 'Dense Theme', - colors: { primary: '#1a73e8' }, - density: 'compact', - })).not.toThrow(); - }); - it('should accept theme with WCAG contrast level', () => { - expect(() => ThemeSchema.parse({ - name: 'accessible_theme', - label: 'Accessible Theme', - colors: { primary: '#000000' }, - wcagContrast: 'AAA', - })).not.toThrow(); - }); - it('should accept theme with RTL', () => { - expect(() => ThemeSchema.parse({ - name: 'arabic_theme', - label: 'Arabic Theme', - colors: { primary: '#1a73e8' }, - rtl: true, - })).not.toThrow(); - }); - it('should accept theme with all new properties', () => { - const theme = ThemeSchema.parse({ - name: 'full_theme', - label: 'Full Theme', - colors: { primary: '#1a73e8' }, - density: 'spacious', - wcagContrast: 'AA', - rtl: false, - }); - expect(theme.density).toBe('spacious'); - expect(theme.wcagContrast).toBe('AA'); - expect(theme.rtl).toBe(false); - }); -}); - // ============================================================================ // Issue #6: Easing naming unified to snake_case in theme animation tokens // ============================================================================ @@ -571,7 +447,7 @@ describe('AnimationSchema - snake_case timing keys', () => { }); // ============================================================================ -// Issue #9: ThemeModeSchema / DensityModeSchema / WcagContrastLevelSchema +// Issue #9: ThemeModeSchema — canonical *Schema name with deprecated alias // ============================================================================ describe('ThemeModeSchema (canonical name)', () => { it('should accept valid theme modes', () => { @@ -584,26 +460,3 @@ describe('ThemeModeSchema (canonical name)', () => { expect(ThemeModeSchema).toBe(ThemeMode); }); }); - -describe('DensityModeSchema (canonical name)', () => { - it('should accept valid density modes', () => { - expect(() => DensityModeSchema.parse('compact')).not.toThrow(); - expect(() => DensityModeSchema.parse('regular')).not.toThrow(); - expect(() => DensityModeSchema.parse('spacious')).not.toThrow(); - }); - - it('should be the same as deprecated DensityMode alias', () => { - expect(DensityModeSchema).toBe(DensityMode); - }); -}); - -describe('WcagContrastLevelSchema (canonical name)', () => { - it('should accept AA and AAA', () => { - expect(() => WcagContrastLevelSchema.parse('AA')).not.toThrow(); - expect(() => WcagContrastLevelSchema.parse('AAA')).not.toThrow(); - }); - - it('should be the same as deprecated WcagContrastLevel alias', () => { - expect(WcagContrastLevelSchema).toBe(WcagContrastLevel); - }); -}); diff --git a/packages/spec/src/ui/theme.zod.ts b/packages/spec/src/ui/theme.zod.ts index 0f2e7dc490..04a26d2d3b 100644 --- a/packages/spec/src/ui/theme.zod.ts +++ b/packages/spec/src/ui/theme.zod.ts @@ -2,8 +2,6 @@ import { z } from 'zod'; import { SnakeCaseIdentifierSchema } from '../shared/identifiers.zod'; -import { TouchTargetConfigSchema } from './touch.zod'; -import { FocusManagementSchema } from './keyboard.zod'; /** * Color Palette Schema @@ -80,26 +78,6 @@ export const TypographySchema = lazySchema(() => z.object({ }).optional(), })); -/** - * Spacing Units Schema - * Defines spacing scale for margins, padding, gaps. - */ -export const SpacingSchema = lazySchema(() => z.object({ - '0': z.string().optional().describe('0 spacing (0)'), - '1': z.string().optional().describe('Spacing unit 1 (e.g., 0.25rem)'), - '2': z.string().optional().describe('Spacing unit 2 (e.g., 0.5rem)'), - '3': z.string().optional().describe('Spacing unit 3 (e.g., 0.75rem)'), - '4': z.string().optional().describe('Spacing unit 4 (e.g., 1rem)'), - '5': z.string().optional().describe('Spacing unit 5 (e.g., 1.25rem)'), - '6': z.string().optional().describe('Spacing unit 6 (e.g., 1.5rem)'), - '8': z.string().optional().describe('Spacing unit 8 (e.g., 2rem)'), - '10': z.string().optional().describe('Spacing unit 10 (e.g., 2.5rem)'), - '12': z.string().optional().describe('Spacing unit 12 (e.g., 3rem)'), - '16': z.string().optional().describe('Spacing unit 16 (e.g., 4rem)'), - '20': z.string().optional().describe('Spacing unit 20 (e.g., 5rem)'), - '24': z.string().optional().describe('Spacing unit 24 (e.g., 6rem)'), -})); - /** * Border Radius Schema * Rounded corners configuration. @@ -130,19 +108,6 @@ export const ShadowSchema = lazySchema(() => z.object({ inner: z.string().optional().describe('Inner shadow (inset)'), })); -/** - * Breakpoints Schema - * Responsive design breakpoints. - */ -export const BreakpointsSchema = lazySchema(() => z.object({ - xs: z.string().optional().describe('Extra small breakpoint (e.g., 480px)'), - sm: z.string().optional().describe('Small breakpoint (e.g., 640px)'), - md: z.string().optional().describe('Medium breakpoint (e.g., 768px)'), - lg: z.string().optional().describe('Large breakpoint (e.g., 1024px)'), - xl: z.string().optional().describe('Extra large breakpoint (e.g., 1280px)'), - '2xl': z.string().optional().describe('2X large breakpoint (e.g., 1536px)'), -})); - /** * Animation Schema * Animation timing and duration settings. @@ -186,27 +151,14 @@ export const ThemeModeSchema = lazySchema(() => z.enum(['light', 'dark', 'auto'] /** @deprecated Use ThemeModeSchema instead */ export const ThemeMode = ThemeModeSchema; -/** - * Density Mode Schema - * Controls spacing and sizing for different use cases. - */ -export const DensityModeSchema = lazySchema(() => z.enum(['compact', 'regular', 'spacious'])); - -/** @deprecated Use DensityModeSchema instead */ -export const DensityMode = DensityModeSchema; - -/** - * WCAG Contrast Level Schema - * Web Content Accessibility Guidelines color contrast requirements. - */ -export const WcagContrastLevelSchema = lazySchema(() => z.enum(['AA', 'AAA'])); - -/** @deprecated Use WcagContrastLevelSchema instead */ -export const WcagContrastLevel = WcagContrastLevelSchema; - /** * Theme Configuration Schema * Complete theme definition for brand customization. + * + * #3494: the aspirational props `spacing`, `breakpoints`, `logo`, `density`, + * `wcagContrast`, `rtl`, `touchTarget` and `keyboardNavigation` were removed — + * the theme engine (objectui generateThemeVars) never consumed them, so + * authoring them was a silent no-op (liveness audit #1878/#1893). */ export const ThemeSchema = lazySchema(() => z.object({ name: SnakeCaseIdentifierSchema.describe('Unique theme identifier (snake_case)'), @@ -221,19 +173,13 @@ export const ThemeSchema = lazySchema(() => z.object({ /** Typography */ typography: TypographySchema.optional().describe('Typography settings'), - - /** Spacing */ - spacing: SpacingSchema.optional().describe('Spacing scale. [EXPERIMENTAL — not enforced] generateThemeVars does not emit spacing tokens yet (liveness audit #1878/#1893).'), - + /** Border radius */ borderRadius: BorderRadiusSchema.optional().describe('Border radius scale'), /** Shadows */ shadows: ShadowSchema.optional().describe('Box shadow effects'), - - /** Breakpoints */ - breakpoints: BreakpointsSchema.optional().describe('Responsive breakpoints. [EXPERIMENTAL — not enforced] generateThemeVars does not emit breakpoint tokens yet (liveness audit #1878/#1893).'), - + /** Animation */ animation: AnimationSchema.optional().describe('Animation settings'), @@ -242,31 +188,9 @@ export const ThemeSchema = lazySchema(() => z.object({ /** Custom CSS variables */ customVars: z.record(z.string(), z.string()).optional().describe('Custom CSS variables (key-value pairs)'), - - /** Logo */ - logo: z.object({ - light: z.string().optional().describe('Logo URL for light mode'), - dark: z.string().optional().describe('Logo URL for dark mode'), - favicon: z.string().optional().describe('Favicon URL'), - }).optional().describe('Logo assets'), - + /** Extends another theme */ extends: z.string().optional().describe('Base theme to extend from'), - - /** Display density mode */ - density: DensityModeSchema.optional().describe('Display density: compact, regular, or spacious. [EXPERIMENTAL — not enforced] The theme engine does not consume theme-level density yet; view-level densityMode is a separate live setting (liveness audit #1878/#1893).'), - - /** WCAG contrast level requirement */ - wcagContrast: WcagContrastLevelSchema.optional().describe('WCAG color contrast level (AA or AAA)'), - - /** Right-to-left language support */ - rtl: z.boolean().optional().describe('Enable right-to-left layout direction. [EXPERIMENTAL — not enforced] No renderer reads theme.rtl yet (liveness audit #1878/#1893).'), - - /** Touch target accessibility configuration */ - touchTarget: TouchTargetConfigSchema.optional().describe('Touch target sizing defaults. [EXPERIMENTAL — not enforced] No renderer consumes touchTarget yet (liveness audit #1878/#1893).'), - - /** Keyboard navigation and focus management */ - keyboardNavigation: FocusManagementSchema.optional().describe('Keyboard focus management settings'), })); export type Theme = z.infer; @@ -283,12 +207,8 @@ export function defineTheme(config: z.input): Theme { } export type ColorPalette = z.infer; export type Typography = z.infer; -export type Spacing = z.infer; export type BorderRadius = z.infer; export type Shadow = z.infer; -export type Breakpoints = z.infer; export type Animation = z.infer; export type ZIndex = z.infer; export type ThemeMode = z.infer; -export type DensityMode = z.infer; -export type WcagContrastLevel = z.infer; diff --git a/skills/objectstack-i18n/SKILL.md b/skills/objectstack-i18n/SKILL.md index b0232b5a77..dd0cf7076c 100644 --- a/skills/objectstack-i18n/SKILL.md +++ b/skills/objectstack-i18n/SKILL.md @@ -82,8 +82,6 @@ export default defineStack({ defaultLocale: 'en', supportedLocales: ['en', 'zh-CN', 'ja-JP', 'es-ES'], fallbackLocale: 'en', - fileOrganization: 'per_locale', - messageFormat: 'simple', }, // translations: [MyTranslations], ← register your bundles here (see below) }); @@ -94,10 +92,6 @@ export default defineStack({ | `defaultLocale` | `string` | **required** | Default BCP-47 locale code | | `supportedLocales` | `string[]` | **required** | All supported locales | | `fallbackLocale` | `string` | optional | Fallback when translation missing | -| `fileOrganization` | `'bundled'` \| `'per_locale'` \| `'per_namespace'` | `'per_locale'` | Declared authoring convention — no runtime consumer (see below) | -| `messageFormat` | `'simple'` \| `'icu'` | `'simple'` | `'icu'` is EXPERIMENTAL — not enforced (see Message Interpolation) | -| `lazyLoad` | `boolean` | `false` | Declared only — no runtime consumer yet | -| `cache` | `boolean` | `true` | EXPERIMENTAL — not enforced; no runtime consumer reads it | > **BCP-47 Locale Codes**: Use standard locale tags (e.g., `en-US`, `zh-CN`, `pt-BR`, `en-GB`). @@ -148,9 +142,8 @@ i18n/ **When to use:** Large projects (20+ objects), 5+ locales, team collaboration, CI/CD pipelines. > These are **authoring conventions**: your import graph assembles whichever layout you -> choose into the `TranslationBundle` values you register on the stack. The -> `fileOrganization` config field declares the convention but has no runtime consumer, -> and `FileI18nAdapter`'s `localesDir` loads only flat top-level `{locale}.json` files +> choose into the `TranslationBundle` values you register on the stack. +> `FileI18nAdapter`'s `localesDir` loads only flat top-level `{locale}.json` files > (subdirectories are skipped) — a per-namespace tree must be assembled by your own > imports or build step. @@ -377,14 +370,13 @@ i18n.t('messages.welcome', 'en', { userName: 'Alice' }); // "Welcome, Alice!" ``` -### ICU MessageFormat [EXPERIMENTAL] +### No ICU MessageFormat -`messageFormat: 'icu'` is accepted by the config schema but **not enforced**. The -schema's own liveness annotation reads: "[EXPERIMENTAL — 'icu' not enforced] No ICU -MessageFormat engine is wired; messageFormat:'icu' is accepted but interpolation -falls back to simple substitution." Until an engine ships, author messages for -simple `{{variable}}` substitution — ICU plural/select strings like -`{count, plural, one {1 message} other {# messages}}` will not be evaluated. +There is no ICU MessageFormat engine — interpolation is always simple +`{{variable}}` substitution (the aspirational `messageFormat` config knob was +removed in #3494). Author messages for simple substitution; ICU plural/select +strings like `{count, plural, one {1 message} other {# messages}}` will not be +evaluated. To pluralize, select the form in application code before calling `t()`. --- @@ -543,7 +535,7 @@ Use this structure for metadata apps: | Layer | CRM Pattern | |:--|:--| -| Stack config | `i18n.fileOrganization = 'per_locale'` with explicit locale list | +| Stack config | `i18n` with an explicit locale list; per-locale source files by convention | | Translation assembly | One `defineTranslationBundle` call that imports per-locale files | | Locale content | Object-scoped translations (`objects.account.fields.*`, `_views`, `_actions`) + global app/messages | | Naming integrity | Translation object/field keys exactly match metadata machine names | diff --git a/skills/objectstack-ui/references/_index.md b/skills/objectstack-ui/references/_index.md index 39919f86b6..26456bf8cf 100644 --- a/skills/objectstack-ui/references/_index.md +++ b/skills/objectstack-ui/references/_index.md @@ -35,10 +35,8 @@ from `node_modules` — there is no local copy in the skill bundle. - `node_modules/@objectstack/spec/src/shared/identifiers.zod.ts` — System Identifier Schema - `node_modules/@objectstack/spec/src/shared/protection.zod.ts` — Package-level metadata protection (ADR-0010 §3.7 — Phase 4.3) - `node_modules/@objectstack/spec/src/ui/i18n.zod.ts` — I18n Object Schema -- `node_modules/@objectstack/spec/src/ui/keyboard.zod.ts` — Focus Trap Configuration Schema - `node_modules/@objectstack/spec/src/ui/responsive.zod.ts` — Breakpoint Name Enum - `node_modules/@objectstack/spec/src/ui/sharing.zod.ts` — Sharing & Embedding Protocol -- `node_modules/@objectstack/spec/src/ui/touch.zod.ts` — Touch Target Configuration Schema ## How to read these