@declarion/react 0.1.65 → 0.1.68

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.
Files changed (98) hide show
  1. package/README.md +32 -0
  2. package/dist-lib/api/agents.d.ts +18 -3
  3. package/dist-lib/api/auth-redirect.d.ts +3 -0
  4. package/dist-lib/api/auth.d.ts +14 -0
  5. package/dist-lib/api/client.d.ts +30 -0
  6. package/dist-lib/api/data.d.ts +12 -0
  7. package/dist-lib/api/events.d.ts +17 -0
  8. package/dist-lib/api/params.d.ts +9 -3
  9. package/dist-lib/components/agents/AgentChatPanel.d.ts +2 -1
  10. package/dist-lib/components/fields/EmailField.d.ts +1 -1
  11. package/dist-lib/components/fields/PhoneField.d.ts +2 -0
  12. package/dist-lib/components/fields/RichTextField.d.ts +2 -0
  13. package/dist-lib/components/fields/UrlField.d.ts +1 -1
  14. package/dist-lib/components/fields/cells/factories/badge.d.ts +2 -0
  15. package/dist-lib/components/fields/cells/factories/boolPill.d.ts +2 -0
  16. package/dist-lib/components/fields/cells/factories/code.d.ts +2 -0
  17. package/dist-lib/components/fields/cells/factories/email.d.ts +2 -0
  18. package/dist-lib/components/fields/cells/factories/host-icons.d.ts +9 -0
  19. package/dist-lib/components/fields/cells/factories/link.d.ts +2 -0
  20. package/dist-lib/components/fields/cells/factories/markdown.d.ts +2 -0
  21. package/dist-lib/components/fields/cells/factories/money.d.ts +2 -0
  22. package/dist-lib/components/fields/cells/factories/multilangInline.d.ts +2 -0
  23. package/dist-lib/components/fields/cells/factories/nextRun.d.ts +2 -0
  24. package/dist-lib/components/fields/cells/factories/number.d.ts +2 -0
  25. package/dist-lib/components/fields/cells/factories/passwordMask.d.ts +2 -0
  26. package/dist-lib/components/fields/cells/factories/phone.d.ts +2 -0
  27. package/dist-lib/components/fields/cells/factories/refLink.d.ts +2 -0
  28. package/dist-lib/components/fields/cells/factories/richTextPreview.d.ts +2 -0
  29. package/dist-lib/components/fields/cells/factories/statusPill.d.ts +2 -0
  30. package/dist-lib/components/fields/cells/factories/tagList.d.ts +2 -0
  31. package/dist-lib/components/fields/cells/factories/text.d.ts +2 -0
  32. package/dist-lib/components/fields/cells/factories/timestamp.d.ts +3 -0
  33. package/dist-lib/components/fields/cells/factories/url-helpers.d.ts +12 -0
  34. package/dist-lib/components/fields/cells/factories/url.d.ts +2 -0
  35. package/dist-lib/components/fields/cells/registry.d.ts +18 -0
  36. package/dist-lib/components/fields/index.d.ts +9 -3
  37. package/dist-lib/components/file-widgets/CircleImage.d.ts +2 -0
  38. package/dist-lib/components/file-widgets/DocumentCard.d.ts +2 -0
  39. package/dist-lib/components/file-widgets/FileList.d.ts +2 -0
  40. package/dist-lib/components/file-widgets/Grid.d.ts +2 -0
  41. package/dist-lib/components/file-widgets/ImageWidget.d.ts +12 -0
  42. package/dist-lib/components/file-widgets/LabeledSlots.d.ts +2 -0
  43. package/dist-lib/components/file-widgets/SquareImage.d.ts +2 -0
  44. package/dist-lib/components/file-widgets/WideBanner.d.ts +2 -0
  45. package/dist-lib/components/file-widgets/builtins.d.ts +1 -0
  46. package/dist-lib/components/file-widgets/cropper.d.ts +14 -0
  47. package/dist-lib/components/file-widgets/draft-scope.d.ts +22 -0
  48. package/dist-lib/components/file-widgets/helpers.d.ts +12 -0
  49. package/dist-lib/components/file-widgets/host.d.ts +23 -0
  50. package/dist-lib/components/file-widgets/index.d.ts +16 -0
  51. package/dist-lib/components/file-widgets/payload.d.ts +21 -0
  52. package/dist-lib/components/file-widgets/registry.d.ts +5 -0
  53. package/dist-lib/components/file-widgets/types.d.ts +15 -0
  54. package/dist-lib/components/file-widgets/upload.d.ts +25 -0
  55. package/dist-lib/components/file-widgets/value.d.ts +4 -0
  56. package/dist-lib/components/layout/ImpersonationBanner.d.ts +11 -0
  57. package/dist-lib/components/layout/ImpersonationFrame.d.ts +11 -0
  58. package/dist-lib/components/layout/ImpersonationStartModal.d.ts +22 -0
  59. package/dist-lib/components/layout/TenantChip.d.ts +9 -1
  60. package/dist-lib/components/list/QuickPeek.d.ts +13 -6
  61. package/dist-lib/components/pages/ExportMenu.d.ts +32 -0
  62. package/dist-lib/components/pages/SmartDetailPage.d.ts +1 -1
  63. package/dist-lib/components/pages/SmartListPage.d.ts +1 -1
  64. package/dist-lib/components/pages/auth/AuthShell.d.ts +17 -2
  65. package/dist-lib/components/pages/auth/Reset.d.ts +1 -0
  66. package/dist-lib/components/pages/exportRequest.d.ts +71 -0
  67. package/dist-lib/components/primitives/BrandGlyph.d.ts +8 -0
  68. package/dist-lib/components/primitives/BrandLogo.d.ts +1 -5
  69. package/dist-lib/components/primitives/Fields.d.ts +3 -3
  70. package/dist-lib/components/primitives/MenuItem.d.ts +2 -1
  71. package/dist-lib/components/primitives/index.d.ts +2 -0
  72. package/dist-lib/components/shared/EmptyState.d.ts +1 -1
  73. package/dist-lib/components/shell/CommandPalette.d.ts +2 -1
  74. package/dist-lib/declarion-glyph.svg +17 -0
  75. package/dist-lib/declarion-react.css +1 -1
  76. package/dist-lib/hooks/useAgentConversation.d.ts +9 -1
  77. package/dist-lib/hooks/useImpersonation.d.ts +23 -0
  78. package/dist-lib/hooks/useParams.d.ts +42 -1
  79. package/dist-lib/hooks/useUserLocale.d.ts +1 -0
  80. package/dist-lib/hooks/useVersionWatcher.d.ts +30 -0
  81. package/dist-lib/index.d.ts +8 -1
  82. package/dist-lib/index.js +9011 -4246
  83. package/dist-lib/index.js.map +1 -1
  84. package/dist-lib/lib/rich-text.d.ts +14 -0
  85. package/dist-lib/lib/safe-url.d.ts +1 -0
  86. package/dist-lib/lib/timestamp.d.ts +17 -0
  87. package/dist-lib/lib/versionTracker.d.ts +66 -0
  88. package/dist-lib/stores/appearance.d.ts +3 -0
  89. package/dist-lib/types/api.d.ts +65 -0
  90. package/dist-lib/types/fieldType.d.ts +2 -0
  91. package/dist-lib/types/files.d.ts +76 -0
  92. package/dist-lib/types/schema.d.ts +64 -0
  93. package/dist-lib/vite/declarion-glyph.svg +17 -0
  94. package/dist-lib/vite/fingerprint-plugin.d.ts +45 -0
  95. package/dist-lib/vite/index.d.ts +21 -0
  96. package/dist-lib/vite/index.js +52 -0
  97. package/dist-lib/vite/index.js.map +1 -0
  98. package/package.json +12 -4
@@ -0,0 +1,14 @@
1
+ import type { HydratedFile } from "../types/files";
2
+ export interface RichTextValue {
3
+ markdown: string;
4
+ files: Record<string, HydratedFile>;
5
+ }
6
+ export declare function richTextMarkdown(value: unknown): string;
7
+ export declare function richTextFiles(value: unknown): Record<string, HydratedFile>;
8
+ export declare function createRichTextValue(markdownValue: unknown, inlineFilesValue: unknown): RichTextValue;
9
+ export declare function withRichTextFile(value: unknown, file: HydratedFile): RichTextValue;
10
+ export declare function withoutRichTextFiles(value: unknown, removedIds: Iterable<string>): RichTextValue;
11
+ export declare function inlineFilesById(value: unknown): Record<string, HydratedFile>;
12
+ export declare function extractDeclarionFileIds(markdown: string): string[];
13
+ export declare function extractDeclarionFileId(url: string | undefined): string | null;
14
+ export declare function richTextTokenForFile(file: Pick<HydratedFile, "id" | "content_type" | "filename">): string;
@@ -0,0 +1 @@
1
+ export declare function safeExternalUrl(input: string | undefined | null): string | undefined;
@@ -18,3 +18,20 @@ export declare function formatWireDateTime(date?: Date): string;
18
18
  * Falls back to browser-local formatting for bare datetime strings.
19
19
  */
20
20
  export declare function formatOffsetTimestamp(raw: string): string;
21
+ /**
22
+ * Relative-or-absolute timestamp formatting for the time_ago cell renderer.
23
+ *
24
+ * Returns a short relative phrase ("5 minutes ago", "2 days ago") when the
25
+ * timestamp is within the last 30 days, and an ISO date ("2026-01-15") when
26
+ * older. Linear / Stripe / Datadog convention — avoids the "1y 4mo 12d ago"
27
+ * eyesore for stable historical data.
28
+ *
29
+ * `absolute` is the offset-aware long form intended for the cell's hover
30
+ * tooltip; the user always has access to the precise timestamp.
31
+ *
32
+ * Bare or unparseable input falls back to the raw string for both fields.
33
+ */
34
+ export declare function formatRelativeOrAbsolute(raw: string | Date | null | undefined, now?: Date): {
35
+ text: string;
36
+ absolute: string;
37
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * versionTracker — central staleness-detection state for the SPA.
3
+ *
4
+ * Every API response carries `X-Declarion-Version: <combined-fingerprint>`
5
+ * (set by the server's version_header middleware). The FIRST arrival
6
+ * in a bundle's lifetime is captured as the baseline; any later
7
+ * response with a different value triggers drift detection — the SPA
8
+ * either silently invalidates the schema query (if only schema differs)
9
+ * or prompts the user to reload (if assets/binary changed).
10
+ *
11
+ * **State is in-memory only — no sessionStorage / localStorage.** Each
12
+ * bundle load captures its own baseline from its first response. The
13
+ * bundle IS its own version contract; the live server's first answer
14
+ * defines what version the bundle should consider itself. Persisting
15
+ * across reloads is actively harmful: a stale baseline from a previous
16
+ * bundle could override the live answer, producing a "click Reload →
17
+ * page reloads → toast pops again" infinite loop. This is the same
18
+ * lesson behind why `index.html` is `Cache-Control: no-cache` — the
19
+ * bootstrap manifest must always be live, never replayed.
20
+ *
21
+ * Defense in depth runs three sources into the same comparator:
22
+ * 1. observe() — header read on every API response (apiFetch hook).
23
+ * 2. fetchProbe() — explicit GET /api/version (focus / online / 60s).
24
+ * 3. (optional) SSE `version-changed` event from useSSE.
25
+ *
26
+ * Cross-tab coordination via BroadcastChannel('declarion-version') —
27
+ * runtime IPC only, not persistence. When one tab detects drift it
28
+ * fans the decision out so all tabs reload together (avoids the
29
+ * half-old / half-new tab anti-pattern). Each tab still bootstraps
30
+ * its own baseline independently.
31
+ */
32
+ export interface VersionDelta {
33
+ /** Reason the delta was emitted. Lets subscribers decide UX. */
34
+ kind: "schema-only" | "asset-or-binary" | "unknown";
35
+ /** Latest combined fingerprint observed. Drives the next baseline. */
36
+ observed: string;
37
+ /** Per-component breakdown when /api/version was reached. */
38
+ components?: VersionComponents;
39
+ }
40
+ export interface VersionComponents {
41
+ schema: string;
42
+ asset: string;
43
+ binary: string;
44
+ }
45
+ export type VersionDeltaListener = (delta: VersionDelta) => void;
46
+ /**
47
+ * Observe an API response. Captures the first-seen fingerprint and
48
+ * compares subsequent ones. Idempotent — safe to call from every
49
+ * apiFetch round-trip; backward-compatible — no-op when the header
50
+ * is absent (older server).
51
+ */
52
+ export declare function observe(headers: Headers | undefined | null): void;
53
+ /**
54
+ * Hit /api/version with If-None-Match (cheap when nothing changed —
55
+ * 304 with no body). On any change, emit a typed delta.
56
+ */
57
+ export declare function fetchProbe(): Promise<void>;
58
+ /**
59
+ * Subscribe to drift events. Called once from useVersionWatcher; the
60
+ * subscriber renders the UpdatePrompt (toast or silent invalidation).
61
+ */
62
+ export declare function subscribe(cb: VersionDeltaListener): () => void;
63
+ /**
64
+ * Test-only: reset internal state. Not exposed via package exports.
65
+ */
66
+ export declare function __resetForTests(): void;
@@ -4,6 +4,7 @@ export type Density = "compact" | "comfortable" | "cozy";
4
4
  export type SidebarMode = "wide" | "collapsed";
5
5
  export type TabsPosition = "top" | "bottom";
6
6
  export type AiSurface = "command" | "panel" | "both" | "off";
7
+ export type FaviconSource = "google" | "direct" | "none";
7
8
  export interface Appearance {
8
9
  theme: ThemeId;
9
10
  accent: AccentId;
@@ -16,6 +17,8 @@ export interface Appearance {
16
17
  radius: number;
17
18
  zebra: boolean;
18
19
  aiSurface: AiSurface;
20
+ /** Favicon source for unknown hosts in URL cells. See ui_favicon_source. */
21
+ faviconSource: FaviconSource;
19
22
  }
20
23
  export declare function applyAppearance(a: Appearance): void;
21
24
  interface AppearanceState extends Appearance {
@@ -1,4 +1,5 @@
1
1
  import type { AccentDef, LocalizedString } from "./schema";
2
+ import type { HydratedFile } from "./files";
2
3
  export interface ListMeta {
3
4
  total: number;
4
5
  page: number;
@@ -47,9 +48,26 @@ export interface AuthUser {
47
48
  roles: string[];
48
49
  permissions?: string[];
49
50
  tenant?: Tenant;
51
+ /** Populated only when the request is acting under an impersonation session. */
52
+ impersonation?: ImpersonationInfo;
50
53
  created_at: string;
51
54
  updated_at: string;
52
55
  }
56
+ /**
57
+ * ImpersonationInfo is the read-side projection of the active session
58
+ * surfaced by auth.me. Drives the banner, topbar chip, viewport frame,
59
+ * and the "Exit impersonation" menu item.
60
+ */
61
+ export interface ImpersonationInfo {
62
+ session_id: string;
63
+ real_user_id: string;
64
+ real_user_email?: string;
65
+ target_user_id: string;
66
+ target_user_email?: string;
67
+ reason: string;
68
+ started_at: string;
69
+ expires_at: string;
70
+ }
53
71
  export interface LoginResponse {
54
72
  expires_at: string;
55
73
  user: AuthUser;
@@ -81,24 +99,70 @@ export interface PublicParam {
81
99
  description?: LocalizedString;
82
100
  value: unknown;
83
101
  }
102
+ /** Per-app legal/support URL set rendered in the auth-shell footer. */
103
+ export interface PublicLegalLinks {
104
+ security_url?: string;
105
+ privacy_url?: string;
106
+ terms_url?: string;
107
+ docs_url?: string;
108
+ }
84
109
  /** Subset of branding exposed pre-login by /api/params/public. */
85
110
  export interface PublicBranding {
86
111
  app_name?: string;
87
112
  app_logo?: string;
88
113
  app_initial?: string;
89
114
  accent_color?: string;
115
+ ai_name?: string;
90
116
  wordmark?: string;
91
117
  version_label?: string;
118
+ status_url?: string;
92
119
  auth_tagline?: LocalizedString;
93
120
  auth_subtagline?: LocalizedString;
94
121
  auth_badges?: LocalizedString[];
122
+ legal_entity?: string;
123
+ workspace_domain?: string;
124
+ legal_links?: PublicLegalLinks;
125
+ }
126
+ /**
127
+ * Deploy-time identity exposed pre-login by /api/params/public so the
128
+ * auth shell can render real region/build values (no mockup placeholders).
129
+ * Each field is empty when unconfigured/unset; consumers hide the pill
130
+ * when the entire block is missing or empty.
131
+ */
132
+ export interface PublicMeta {
133
+ version?: string;
134
+ revision?: string;
135
+ build_time?: string;
136
+ region?: string;
95
137
  }
96
138
  /** Envelope returned by GET /api/params/public. */
97
139
  export interface PublicParamsResponse {
98
140
  parameters: PublicParam[];
99
141
  accents?: Record<string, AccentDef>;
100
142
  branding?: PublicBranding;
143
+ meta?: PublicMeta;
144
+ }
145
+ /**
146
+ * Wire envelope for GET /api/params/{code}.
147
+ *
148
+ * The server always responds with HTTP 200. `found:false` means the
149
+ * platform has no value for the code (undeclared, restricted-category,
150
+ * or no value at any resolution layer); the caller substitutes its own
151
+ * default. `found:true` means `value` is authoritative - even if it is
152
+ * null/zero (an explicit nil value is a real value, not absence).
153
+ *
154
+ * The default never crosses the wire; servers don't track caller
155
+ * defaults, so two callers with different defaults always see consistent
156
+ * server state.
157
+ */
158
+ export interface ParamLookup {
159
+ code: string;
160
+ found: boolean;
161
+ value?: unknown;
162
+ source?: "object" | "user" | "role" | "tenant" | "env" | "yaml";
163
+ type?: string;
101
164
  }
165
+ /** @deprecated Prefer `ParamLookup`. Retained for callers still inspecting `source`. */
102
166
  export interface ResolvedParam {
103
167
  code: string;
104
168
  name: LocalizedString;
@@ -199,6 +263,7 @@ export type Units = Record<string, string>;
199
263
  /** A row enriched with $-prefixed sub-resources (as returned by Get or write responses). */
200
264
  export interface EnrichedRow extends Record<string, unknown> {
201
265
  $refs?: Refs;
266
+ $files?: Record<string, HydratedFile | HydratedFile[] | null>;
202
267
  $statuses?: V2StatusInstance[];
203
268
  $properties?: V2PropertyInstance[];
204
269
  $params?: V2ParamInstance[];
@@ -0,0 +1,2 @@
1
+ export type FieldType = "uuid" | "string" | "text" | "rich_text" | "int" | "float" | "bool" | "date" | "time" | "timestamp" | "email" | "url" | "phone" | "enum" | "json" | "ref" | "tags" | "multilang" | "multilang_text" | "structure" | "secret" | "password" | "decimal" | "string_array" | "int_array";
2
+ export declare const FIELD_TYPES: readonly FieldType[];
@@ -0,0 +1,76 @@
1
+ export type FileFieldMode = "single" | "array" | "named_map" | "rich_text_inline";
2
+ export interface FileFieldDisplay {
3
+ widget: string;
4
+ placeholder?: string;
5
+ crop_on_upload?: boolean;
6
+ aspect_ratio?: string;
7
+ focal_point?: boolean;
8
+ is_listable?: boolean;
9
+ listable_thumbnail?: string;
10
+ reorderable?: boolean;
11
+ bulk_upload?: boolean;
12
+ bulk_download?: boolean;
13
+ lazy_load?: boolean;
14
+ }
15
+ export interface FileNamedSlotDef {
16
+ accepts?: string[];
17
+ max_size_bytes?: number;
18
+ svg_sanitize?: boolean;
19
+ pdf_strip_js?: boolean;
20
+ exif_strip?: boolean;
21
+ aspect_ratio?: string;
22
+ storage_backend?: string;
23
+ }
24
+ export interface FileFieldDef {
25
+ name: string;
26
+ mode: FileFieldMode;
27
+ accepts?: string[];
28
+ max_size_bytes?: number;
29
+ max_count?: number;
30
+ max_total_size_bytes?: number;
31
+ required?: boolean;
32
+ slots?: Record<string, FileNamedSlotDef>;
33
+ display?: FileFieldDisplay;
34
+ }
35
+ export interface HydratedFile {
36
+ id: string;
37
+ filename: string;
38
+ content_type: string;
39
+ size_bytes: number;
40
+ sha256?: string;
41
+ width?: number;
42
+ height?: number;
43
+ page_count?: number;
44
+ uploaded_at?: string;
45
+ uploaded_by?: string;
46
+ url?: string;
47
+ presigned_url?: string;
48
+ derivations?: Record<string, string>;
49
+ metadata?: Record<string, unknown>;
50
+ position?: number;
51
+ slot?: string;
52
+ version?: {
53
+ number?: number;
54
+ is_current?: boolean;
55
+ supersedes_file_id?: string;
56
+ };
57
+ }
58
+ export type NamedFileFieldValue = Record<string, HydratedFile | null>;
59
+ export type FileFieldValue = HydratedFile | HydratedFile[] | NamedFileFieldValue | null;
60
+ export interface UploadProgress {
61
+ loaded: number;
62
+ total: number;
63
+ pct: number;
64
+ }
65
+ export interface UploadedFile {
66
+ id: string;
67
+ filename: string;
68
+ content_type: string;
69
+ size_bytes: number;
70
+ sha256: string;
71
+ dedup?: boolean;
72
+ width?: number;
73
+ height?: number;
74
+ url?: string;
75
+ metadata?: Record<string, unknown>;
76
+ }
@@ -1,6 +1,19 @@
1
1
  import { OrderedMap } from "./ordmap";
2
+ import type { FileFieldDef } from "./files";
2
3
  export { OrderedMap };
3
4
  export type LocalizedString = Record<string, string>;
5
+ /**
6
+ * Per-entity empty-state copy declared in the manifest under
7
+ * `display.empty_state`. Title and Body are localized; LearnMoreURL is a
8
+ * plain string. The SDK falls back to a generic "No <plural> yet" derived
9
+ * from `display.name_plural` when this is absent. Read-only entities never
10
+ * render the legacy "Create your first..." copy regardless of EmptyStateDef.
11
+ */
12
+ export interface EmptyStateDef {
13
+ title?: LocalizedString;
14
+ body?: LocalizedString;
15
+ learn_more_url?: string;
16
+ }
4
17
  /**
5
18
  * Reserved sentinel value for Screen.detail_screen (list screens). Disables
6
19
  * row-click navigation even when the entity declares a canonical detail. Paired
@@ -8,6 +21,12 @@ export type LocalizedString = Record<string, string>;
8
21
  */
9
22
  export declare const DETAIL_SCREEN_NONE = "none";
10
23
  export declare function loc(ls: LocalizedString | undefined, lang?: string): string;
24
+ export declare const DISPLAY_KEY = "$display";
25
+ export declare function getDisplayValue(row: Record<string, unknown> | null | undefined, entity: {
26
+ display?: {
27
+ display_field?: string;
28
+ };
29
+ } | null | undefined): string | undefined;
11
30
  export declare function fieldDisplayName(field: EntityField, fieldName: string, entities?: Record<string, Entity>): string;
12
31
  export interface RefDisplay {
13
32
  field: string;
@@ -39,12 +58,22 @@ export interface EntityField {
39
58
  default?: unknown;
40
59
  display?: {
41
60
  name: LocalizedString;
61
+ description?: LocalizedString;
42
62
  width?: number;
43
63
  widget?: string;
44
64
  is_listable?: boolean;
45
65
  is_filterable?: boolean;
46
66
  ref_display_field?: string;
47
67
  span?: number | "full";
68
+ bool_labels?: {
69
+ true: LocalizedString;
70
+ false: LocalizedString;
71
+ };
72
+ value_map?: Record<string, {
73
+ label?: LocalizedString;
74
+ color?: string;
75
+ }>;
76
+ currency?: string;
48
77
  };
49
78
  values?: Array<{
50
79
  code: string;
@@ -53,6 +82,7 @@ export interface EntityField {
53
82
  color?: string;
54
83
  }>;
55
84
  enum?: string;
85
+ files?: FileFieldDef;
56
86
  ref?: RefConfig;
57
87
  structure?: string;
58
88
  array?: boolean;
@@ -183,6 +213,8 @@ export interface Entity {
183
213
  description?: LocalizedString;
184
214
  icon: string;
185
215
  display_field?: string;
216
+ display_template?: string;
217
+ empty_state?: EmptyStateDef;
186
218
  };
187
219
  fields: OrderedMap<EntityField>;
188
220
  soft_delete?: string;
@@ -194,7 +226,34 @@ export interface Entity {
194
226
  actions?: Record<string, Action>;
195
227
  handlers?: Record<string, HandlerDef>;
196
228
  associations?: Record<string, AssociationDef>;
229
+ files?: OrderedMap<FileFieldDef>;
197
230
  detail_screen?: string;
231
+ /** Standard CSV export gate per docs/plans/2026-05-01-standard-table-export.md.
232
+ * Absent block (or absent `enabled`) means export is enabled. Only an
233
+ * explicit `enabled: false` opts out, mirroring the Go ExportEnabled()
234
+ * helper. Same shape ships on Screen for per-screen overrides. */
235
+ export?: ExportConfig;
236
+ /** At most one row visible per caller. Loader requires either a
237
+ * caller-bound `prefilter` or `access: self` to back this. Frontend
238
+ * routes a singleton entity's detail screen at the screen's declared
239
+ * `route:` without a `:id` segment, and skips list-route registration. */
240
+ singleton?: boolean;
241
+ /** Server-enforced WHERE applied unconditionally on every read/write
242
+ * via server/handlers/data_prefilter.go. Values may be literal scalars
243
+ * OR `$caller.*` expressions (`$caller.user_id`, `$caller.tenant_id`)
244
+ * resolved at request time. Security boundary, not a UX hint. */
245
+ prefilter?: Record<string, unknown>;
246
+ /** Hard entity-wide access gate. `"self"` restricts every read/write
247
+ * to rows where the field marked `owner: true` matches the caller's
248
+ * user id (chokepoint + with_check). `"platform_admin"` restricts to
249
+ * platform owner / superadmin roles. Empty = standard RBAC. */
250
+ access?: "self" | "platform_admin";
251
+ /** Whether the entity participates in RBAC permission gating
252
+ * (`entity:<code>:<op>` grants). Mirrors engine.Entity.Permissions. */
253
+ permissions?: boolean;
254
+ }
255
+ export interface ExportConfig {
256
+ enabled?: boolean;
198
257
  }
199
258
  export declare function getPrimaryKeyFields(entity: Entity): string[];
200
259
  export type PkValues = Record<string, string>;
@@ -301,6 +360,10 @@ export interface Screen {
301
360
  inline_edit?: InlineEditConfig;
302
361
  column_overrides?: Record<string, ColumnSpec>;
303
362
  activity_rail?: ActivityRailConfig;
363
+ /** Per-screen override for the CSV export gate. Same shape as Entity.export.
364
+ * Either gate being false hides the toolbar Export menu and makes the
365
+ * POST endpoint return 403 EXPORT_DISABLED. */
366
+ export?: ExportConfig;
304
367
  }
305
368
  export interface QuickPeekConfig {
306
369
  enabled?: boolean;
@@ -469,6 +532,7 @@ export interface Branding {
469
532
  app_logo?: string;
470
533
  app_initial?: string;
471
534
  accent_color?: string;
535
+ ai_name?: string;
472
536
  wordmark?: string;
473
537
  version_label?: string;
474
538
  status_url?: string;
@@ -0,0 +1,17 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
2
+ <!--
3
+ Declarion mark: angular letter "D" with an embedded "=" in the negative
4
+ space of the counter. Encodes the product thesis ("this declaration
5
+ equals this runtime behavior") in the letterform itself. Solid accent
6
+ purple #5B5BD6 — visible on both light surfaces and the dark brand
7
+ panel without prefers-color-scheme tricks. Trademark distinctive,
8
+ scales crisp from favicon (16px) through hero (96px+).
9
+ -->
10
+ <path
11
+ fill-rule="evenodd"
12
+ fill="#5B5BD6"
13
+ d="M 6 6 H 42 L 58 22 V 42 L 42 58 H 6 Z M 18 18 V 46 H 38 L 46 38 V 26 L 38 18 Z"
14
+ />
15
+ <rect x="22" y="28" width="20" height="3" fill="#5B5BD6"/>
16
+ <rect x="22" y="33" width="20" height="3" fill="#5B5BD6"/>
17
+ </svg>
@@ -0,0 +1,45 @@
1
+ import type { Plugin } from "vite";
2
+ export interface DeclarionFingerprintPluginOptions {
3
+ /**
4
+ * Subdirectory of `outDir` to walk. Defaults to "assets" — Vite's
5
+ * standard hashed-output location. Override only if a consumer has
6
+ * customized `build.assetsDir`.
7
+ */
8
+ assetsDir?: string;
9
+ /**
10
+ * Filename of the emitted fingerprint, written next to `index.html`.
11
+ * Defaults to "bundle-fingerprint.txt". Match this on the server
12
+ * side (`apps/server/fingerprint.go:readBundleFingerprint`) if you
13
+ * customize.
14
+ */
15
+ filename?: string;
16
+ }
17
+ /**
18
+ * Vite plugin that emits a single-line `bundle-fingerprint.txt` next
19
+ * to `index.html` after every production build. The fingerprint is a
20
+ * SHA-256 over every file in `assetsDir/` — sorted by name, length-
21
+ * prefixed (`name\x00sha256\x00`) so concatenation is collision-safe.
22
+ *
23
+ * The Declarion server reads this file at boot and folds it into the
24
+ * combined build identity exposed via `X-Declarion-Version` and
25
+ * `/api/version`. When a consumer redeploys the UI bundle (changed
26
+ * JS/CSS/assets) the file changes → server fingerprint changes → SDK
27
+ * detects asset drift → user is prompted to reload to pick up the
28
+ * new code.
29
+ *
30
+ * Why a separate file rather than walking `assetsDir/` from Go at
31
+ * boot:
32
+ * - Eliminates a per-process I/O dependency on bundle layout
33
+ * conventions (Vite could change `assetsDir` defaults; the
34
+ * plugin is the canonical contract).
35
+ * - Computed once at build time, read once at boot — never on the
36
+ * hot path.
37
+ * - Lets the consumer image ship the fingerprint as an immutable
38
+ * artifact rather than re-deriving it at every server start.
39
+ *
40
+ * Backward compatible: if a consumer hasn't adopted the plugin yet,
41
+ * `bundle-fingerprint.txt` is absent at boot, the server logs once
42
+ * and degrades to binary-only drift detection (still correct — the
43
+ * binary fingerprint differs across builds anyway). No errors.
44
+ */
45
+ export declare function declarionFingerprintPlugin(options?: DeclarionFingerprintPluginOptions): Plugin;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @declarion/react/vite — Vite plugin entry point.
3
+ *
4
+ * Separate from the SDK's main React entry (`@declarion/react`) so this
5
+ * Node-only code never accidentally pulls into a browser bundle.
6
+ *
7
+ * Usage in a consumer's `vite.config.ts`:
8
+ *
9
+ * import { declarionFingerprintPlugin } from "@declarion/react/vite";
10
+ *
11
+ * export default defineConfig({
12
+ * plugins: [react(), declarionFingerprintPlugin()],
13
+ * });
14
+ *
15
+ * One line, one plugin — works in every Declarion deployment mode
16
+ * (local-up, prebuilt-declarion, fully-prebuilt prod) because the
17
+ * plugin runs at consumer-build time regardless of where the SDK
18
+ * itself comes from (npm pin / vite alias / link).
19
+ */
20
+ export { declarionFingerprintPlugin } from "./fingerprint-plugin";
21
+ export type { DeclarionFingerprintPluginOptions } from "./fingerprint-plugin";
@@ -0,0 +1,52 @@
1
+ import { createHash as e } from "node:crypto";
2
+ import { readFileSync as t, readdirSync as n, statSync as r, writeFileSync as i } from "node:fs";
3
+ import { join as a } from "node:path";
4
+ //#region src/vite/fingerprint-plugin.ts
5
+ function o(n = {}) {
6
+ let r = n.filename ?? "bundle-fingerprint.txt";
7
+ return {
8
+ name: "declarion-fingerprint",
9
+ apply: "build",
10
+ closeBundle: {
11
+ sequential: !0,
12
+ handler() {
13
+ let o = this.environment.config, c = o.build.outDir, l = a(c, n.assetsDir ?? o.build.assetsDir ?? "assets"), u = s(l);
14
+ u.sort();
15
+ let d = e("sha256");
16
+ for (let n of u) {
17
+ let r = a(l, n), i = e("sha256").update(t(r)).digest("hex");
18
+ d.update(`${n.length}:${n}\n`), d.update(`${i.length}:${i}\n`);
19
+ }
20
+ let f = d.digest("hex");
21
+ i(a(c, r), f + "\n", "utf8");
22
+ }
23
+ }
24
+ };
25
+ }
26
+ function s(e) {
27
+ let t = [], i = [{
28
+ abs: e,
29
+ rel: ""
30
+ }];
31
+ for (; i.length > 0;) {
32
+ let { abs: e, rel: o } = i.pop(), s;
33
+ try {
34
+ s = n(e);
35
+ } catch (e) {
36
+ if (e && typeof e == "object" && "code" in e && e.code === "ENOENT") return [];
37
+ throw e;
38
+ }
39
+ for (let n of s) {
40
+ let s = a(e, n), c = o ? `${o}/${n}` : n;
41
+ r(s).isDirectory() ? i.push({
42
+ abs: s,
43
+ rel: c
44
+ }) : t.push(c);
45
+ }
46
+ }
47
+ return t;
48
+ }
49
+ //#endregion
50
+ export { o as declarionFingerprintPlugin };
51
+
52
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/vite/fingerprint-plugin.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { createHash } from \"node:crypto\";\nimport {\n readdirSync,\n readFileSync,\n statSync,\n writeFileSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface DeclarionFingerprintPluginOptions {\n /**\n * Subdirectory of `outDir` to walk. Defaults to \"assets\" — Vite's\n * standard hashed-output location. Override only if a consumer has\n * customized `build.assetsDir`.\n */\n assetsDir?: string;\n\n /**\n * Filename of the emitted fingerprint, written next to `index.html`.\n * Defaults to \"bundle-fingerprint.txt\". Match this on the server\n * side (`apps/server/fingerprint.go:readBundleFingerprint`) if you\n * customize.\n */\n filename?: string;\n}\n\n/**\n * Vite plugin that emits a single-line `bundle-fingerprint.txt` next\n * to `index.html` after every production build. The fingerprint is a\n * SHA-256 over every file in `assetsDir/` — sorted by name, length-\n * prefixed (`name\\x00sha256\\x00`) so concatenation is collision-safe.\n *\n * The Declarion server reads this file at boot and folds it into the\n * combined build identity exposed via `X-Declarion-Version` and\n * `/api/version`. When a consumer redeploys the UI bundle (changed\n * JS/CSS/assets) the file changes → server fingerprint changes → SDK\n * detects asset drift → user is prompted to reload to pick up the\n * new code.\n *\n * Why a separate file rather than walking `assetsDir/` from Go at\n * boot:\n * - Eliminates a per-process I/O dependency on bundle layout\n * conventions (Vite could change `assetsDir` defaults; the\n * plugin is the canonical contract).\n * - Computed once at build time, read once at boot — never on the\n * hot path.\n * - Lets the consumer image ship the fingerprint as an immutable\n * artifact rather than re-deriving it at every server start.\n *\n * Backward compatible: if a consumer hasn't adopted the plugin yet,\n * `bundle-fingerprint.txt` is absent at boot, the server logs once\n * and degrades to binary-only drift detection (still correct — the\n * binary fingerprint differs across builds anyway). No errors.\n */\nexport function declarionFingerprintPlugin(\n options: DeclarionFingerprintPluginOptions = {},\n): Plugin {\n const filename = options.filename ?? \"bundle-fingerprint.txt\";\n\n return {\n name: \"declarion-fingerprint\",\n apply: \"build\",\n closeBundle: {\n // `sequential: true` is the safe default — run after rollup has\n // finished writing the bundle, before any other closeBundle\n // hooks observe the output dir.\n sequential: true,\n handler() {\n // Read outDir + assetsDir from the resolved Vite config. We\n // don't capture them in a `configResolved` hook because\n // `closeBundle` already runs after config resolution.\n const cfg = this.environment.config;\n const outDir = cfg.build.outDir;\n const assetsDir = options.assetsDir ?? cfg.build.assetsDir ?? \"assets\";\n const assetsRoot = join(outDir, assetsDir);\n\n const files = collectFiles(assetsRoot);\n files.sort(); // deterministic order\n\n const h = createHash(\"sha256\");\n for (const relPath of files) {\n const abs = join(assetsRoot, relPath);\n const fileHash = createHash(\"sha256\")\n .update(readFileSync(abs))\n .digest(\"hex\");\n // Length-prefix discipline — same as the Go side\n // (store/migrations.go:215-282 + apps/server/fingerprint.go).\n // `a`+`bc` and `ab`+`c` must produce different hashes.\n h.update(`${relPath.length}:${relPath}\\n`);\n h.update(`${fileHash.length}:${fileHash}\\n`);\n }\n const combined = h.digest(\"hex\");\n\n writeFileSync(join(outDir, filename), combined + \"\\n\", \"utf8\");\n },\n },\n };\n}\n\n/**\n * Walks `root` recursively and returns POSIX-style relative paths of\n * every regular file. Sorted by the caller. Symlinks are followed\n * since Vite output dirs don't contain them in practice.\n */\nfunction collectFiles(root: string): string[] {\n const out: string[] = [];\n const stack: { abs: string; rel: string }[] = [{ abs: root, rel: \"\" }];\n while (stack.length > 0) {\n const { abs, rel } = stack.pop()!;\n let entries: string[];\n try {\n entries = readdirSync(abs);\n } catch (err) {\n // Missing assetsDir: nothing to fingerprint. Return empty;\n // the plugin still emits the file so the server boot path\n // doesn't have to special-case missing-yet-defined assets.\n if (\n err &&\n typeof err === \"object\" &&\n \"code\" in err &&\n (err as { code: string }).code === \"ENOENT\"\n ) {\n return [];\n }\n throw err;\n }\n for (const name of entries) {\n const childAbs = join(abs, name);\n const childRel = rel ? `${rel}/${name}` : name;\n if (statSync(childAbs).isDirectory()) {\n stack.push({ abs: childAbs, rel: childRel });\n } else {\n out.push(childRel);\n }\n }\n }\n return out;\n}\n"],"mappings":";;;;AAuDA,SAAgB,EACd,IAA6C,EAAE,EACvC;CACR,IAAM,IAAW,EAAQ,YAAY;AAErC,QAAO;EACL,MAAM;EACN,OAAO;EACP,aAAa;GAIX,YAAY;GACZ,UAAU;IAIR,IAAM,IAAM,KAAK,YAAY,QACvB,IAAS,EAAI,MAAM,QAEnB,IAAa,EAAK,GADN,EAAQ,aAAa,EAAI,MAAM,aAAa,SACpB,EAEpC,IAAQ,EAAa,EAAW;AACtC,MAAM,MAAM;IAEZ,IAAM,IAAI,EAAW,SAAS;AAC9B,SAAK,IAAM,KAAW,GAAO;KAC3B,IAAM,IAAM,EAAK,GAAY,EAAQ,EAC/B,IAAW,EAAW,SAAS,CAClC,OAAO,EAAa,EAAI,CAAC,CACzB,OAAO,MAAM;AAKhB,KADA,EAAE,OAAO,GAAG,EAAQ,OAAO,GAAG,EAAQ,IAAI,EAC1C,EAAE,OAAO,GAAG,EAAS,OAAO,GAAG,EAAS,IAAI;;IAE9C,IAAM,IAAW,EAAE,OAAO,MAAM;AAEhC,MAAc,EAAK,GAAQ,EAAS,EAAE,IAAW,MAAM,OAAO;;GAEjE;EACF;;AAQH,SAAS,EAAa,GAAwB;CAC5C,IAAM,IAAgB,EAAE,EAClB,IAAwC,CAAC;EAAE,KAAK;EAAM,KAAK;EAAI,CAAC;AACtE,QAAO,EAAM,SAAS,IAAG;EACvB,IAAM,EAAE,QAAK,WAAQ,EAAM,KAAK,EAC5B;AACJ,MAAI;AACF,OAAU,EAAY,EAAI;WACnB,GAAK;AAIZ,OACE,KACA,OAAO,KAAQ,YACf,UAAU,KACT,EAAyB,SAAS,SAEnC,QAAO,EAAE;AAEX,SAAM;;AAER,OAAK,IAAM,KAAQ,GAAS;GAC1B,IAAM,IAAW,EAAK,GAAK,EAAK,EAC1B,IAAW,IAAM,GAAG,EAAI,GAAG,MAAS;AAC1C,GAAI,EAAS,EAAS,CAAC,aAAa,GAClC,EAAM,KAAK;IAAE,KAAK;IAAU,KAAK;IAAU,CAAC,GAE5C,EAAI,KAAK,EAAS;;;AAIxB,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@declarion/react",
3
- "version": "0.1.65",
3
+ "version": "0.1.68",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "React SDK for Declarion, the schema-driven business apps platform.",
@@ -15,6 +15,11 @@
15
15
  "import": "./dist-lib/index.js",
16
16
  "default": "./dist-lib/index.js"
17
17
  },
18
+ "./vite": {
19
+ "types": "./dist-lib/vite/index.d.ts",
20
+ "import": "./dist-lib/vite/index.js",
21
+ "default": "./dist-lib/vite/index.js"
22
+ },
18
23
  "./styles.css": "./dist-lib/declarion-react.css"
19
24
  },
20
25
  "files": [
@@ -67,6 +72,8 @@
67
72
  "@types/react": "^19.0.0",
68
73
  "@types/react-dom": "^19.0.0",
69
74
  "@vitejs/plugin-react": "^6.0.1",
75
+ "eslint": "^10.2.1",
76
+ "eslint-plugin-react-hooks": "^7.1.1",
70
77
  "jsdom": "^29.0.1",
71
78
  "msw": "^2.13.4",
72
79
  "react": "^19.0.0",
@@ -74,14 +81,15 @@
74
81
  "tailwindcss": "^4.2.4",
75
82
  "tsc-alias": "^1.8.16",
76
83
  "typescript": "^6.0.3",
84
+ "typescript-eslint": "^8.59.0",
77
85
  "vite": "^8.0.9",
78
86
  "vitest": "^4.1.5"
79
87
  },
80
88
  "scripts": {
81
89
  "dev": "vite",
82
- "build": "tsc --noEmit && vite build",
83
- "build:lib": "vite build --config vite.lib.config.ts && tsc -p tsconfig.lib.json && tsc-alias -p tsconfig.lib.json",
84
- "lint": "tsc --noEmit",
90
+ "build": "tsc -p tsconfig.app.json --noEmit && vite build",
91
+ "build:lib": "vite build --config vite.lib.config.ts && vite build --config vite.plugin.config.ts && tsc -p tsconfig.lib.json && tsc-alias -p tsconfig.lib.json",
92
+ "lint": "tsc --noEmit && eslint src",
85
93
  "test": "vitest run",
86
94
  "preview": "vite preview"
87
95
  }