create-flowdular 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-template/.agents/skills/agent-tool-design/SKILL.md +1 -1
- package/agent-template/.agents/skills/auth-security-review/SKILL.md +1 -1
- package/agent-template/.agents/skills/deploy-operate/SKILL.md +109 -0
- package/agent-template/.agents/skills/module-new/SKILL.md +29 -0
- package/agent-template/.agents/skills/module-update/SKILL.md +9 -1
- package/agent-template/.agents/skills/spec-interview/SKILL.md +114 -0
- package/agent-template/.agents/skills/ux-design/SKILL.md +34 -3
- package/agent-template/.ai/README.md +2 -1
- package/agent-template/.ai/agents/sandbox/business-manager.md +5 -1
- package/agent-template/.ai/blueprints/author-spec/README.md +1 -1
- package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +44 -0
- package/agent-template/.ai/blueprints/author-spec/steps.yaml +5 -5
- package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +99 -3
- package/agent-template/.ai/blueprints/edit-module/gates.yaml +4 -0
- package/agent-template/.ai/blueprints/edit-module/required-files.yaml +9 -0
- package/agent-template/.ai/blueprints/new-module/gates.yaml +4 -0
- package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +2 -2
- package/agent-template/.ai/blueprints/release/gates.yaml +4 -0
- package/agent-template/.ai/platform-capabilities.md +128 -0
- package/agent-template/.ai/policies/capabilities.yaml +100 -0
- package/agent-template/.ai/policies/path-ownership.yaml +5 -2
- package/agent-template/.ai/policies/task-budgets.yaml +5 -3
- package/agent-template/.ai/references/catalog/module.json +4 -4
- package/agent-template/.ai/references/catalog/package.json +2 -2
- package/agent-template/.ai/references/catalog/spec/module.yaml +5 -3
- package/agent-template/.ai/references/catalog/src/platform.ts +2 -0
- package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +89 -1
- package/agent-template/.ai/references/catalog/src/services/data-classes.ts +47 -0
- package/agent-template/.ai/references/catalog/src/services/database-repository.ts +98 -1
- package/agent-template/.ai/references/catalog/src/services/repository.ts +22 -1
- package/agent-template/.ai/references/catalog/tests/data-classes.test.ts +157 -0
- package/agent-template/.ai/references/catalog.provenance.json +12 -10
- package/agent-template/.ai/rules/flowdular.md +4 -0
- package/agent-template/.ai/skills/README.md +10 -0
- package/agent-template/.ai/skills/agent-tool-design/SKILL.md +1 -2
- package/agent-template/.ai/skills/auth-security-review/SKILL.md +1 -1
- package/agent-template/.ai/skills/business-agent-design/SKILL.md +0 -1
- package/agent-template/.ai/skills/deploy-operate/SKILL.md +114 -0
- package/agent-template/.ai/skills/module-new/SKILL.md +29 -3
- package/agent-template/.ai/skills/module-update/SKILL.md +9 -3
- package/agent-template/.ai/skills/perf-audit/SKILL.md +0 -1
- package/agent-template/.ai/skills/release-eject-pr/SKILL.md +0 -1
- package/agent-template/.ai/skills/spec-interview/SKILL.md +120 -0
- package/agent-template/.ai/skills/test-hardening/SKILL.md +1 -0
- package/agent-template/.ai/skills/ux-design/SKILL.md +34 -3
- package/agent-template/.ai/skills/variables/SKILL.md +0 -2
- package/agent-template/.ai/skills/workflow-development/SKILL.md +0 -1
- package/agent-template/.claude/skills/agent-tool-design/SKILL.md +1 -1
- package/agent-template/.claude/skills/auth-security-review/SKILL.md +1 -1
- package/agent-template/.claude/skills/deploy-operate/SKILL.md +109 -0
- package/agent-template/.claude/skills/module-new/SKILL.md +29 -0
- package/agent-template/.claude/skills/module-update/SKILL.md +9 -1
- package/agent-template/.claude/skills/spec-interview/SKILL.md +114 -0
- package/agent-template/.claude/skills/ux-design/SKILL.md +34 -3
- package/agent-template/AGENTS.md +4 -0
- package/agent-template/CLAUDE.md +4 -0
- package/agent-template/docs/adr/0003-module-settings.md +1 -1
- package/agent-template/docs/adr/0006-agentic-workflows.md +24 -21
- package/agent-template/docs/agent-contract.md +2 -2
- package/agent-template/docs/cli-extensions.md +82 -0
- package/agent-template/docs/cli.md +190 -0
- package/agent-template/docs/configuration.md +593 -36
- package/agent-template/docs/design-system.md +185 -31
- package/agent-template/docs/getting-started.md +118 -0
- package/agent-template/docs/module-distribution.md +96 -0
- package/agent-template/docs/module-web-surfaces.md +221 -0
- package/agent-template/docs/modules.md +216 -0
- package/agent-template/docs/operations.md +464 -0
- package/agent-template/docs/sandbox.md +212 -0
- package/agent-template/platform/scripts/build.mjs +10 -0
- package/dist/bin.js +28 -0
- package/package.json +1 -1
- package/template/default/.dockerignore +14 -0
- package/template/default/.env.example +94 -0
- package/template/default/README.md +37 -1
- package/template/default/flowdular.json +15 -4
- package/template/default/infra/README.md +116 -0
- package/template/default/infra/docker/Dockerfile +37 -0
- package/template/default/infra/docker/compose.yaml +158 -0
- package/template/default/infra/docker/postgres/10-roles.sh +31 -0
- package/template/default/infra/docker/postgres/tls-init.sh +28 -0
- package/template/default/infra/kubernetes/database-secret.example.yaml +15 -0
- package/template/default/infra/kubernetes/deployment.yaml +211 -0
- package/template/default/infra/kubernetes/kustomization.yaml +9 -0
- package/template/default/infra/kubernetes/secrets.example.yaml +52 -0
- package/template/default/infra/kubernetes/service.yaml +13 -0
- package/template/default/modules/example/module.json +2 -1
- package/template/default/modules/example/package.json +1 -1
- package/template/default/modules/example/spec/module.yaml +1 -1
- package/template/default/modules/example/src/services/database-repository.ts +2 -12
- package/template/default/package.json +3 -2
- package/template/default/platform/octane.config.ts +99 -9
- package/template/default/platform/package.json +1 -1
- package/template/default/platform/src/generated/modules.client.ts +26 -2
- package/template/default/platform/src/generated/modules.server.ts +241 -10
- package/template/default/platform/src/server/health.ts +47 -0
- package/template/default/platform/src/server/metrics.ts +100 -0
- package/template/default/platform/src/server/storage.ts +172 -0
- package/template/default/platform/src/server/tracing.ts +85 -0
- package/template/default/specs/application.yaml +15 -0
|
@@ -54,10 +54,24 @@ shared primitives, tokens, and the rules for using them.
|
|
|
54
54
|
13. Destructive actions close a drawer in their own `ui-form__section--danger`:
|
|
55
55
|
one title, one line of consequence, one `Button variant="danger"`. They are
|
|
56
56
|
never mixed into a status row.
|
|
57
|
-
14.
|
|
57
|
+
14. A screen never writes a bare `select.ui-select`, a bare date input, a bare
|
|
58
|
+
file input, or its own label for any of them: it uses `Select`,
|
|
59
|
+
`DateField`, `DateRangeField`, `DatePicker`, or `FileUpload`, which own the
|
|
60
|
+
label association, the invalid state, and the help or error line. The only
|
|
61
|
+
bare `ui-select` left is the one a primitive renders inside itself (the
|
|
62
|
+
`Pagination` page size), where the row is the label and the control carries
|
|
63
|
+
`aria-label`.
|
|
64
|
+
15. Menus (workspace switcher, account menu, session actions) are `ui-menu`
|
|
58
65
|
with `ui-menu__item` rows: 40 px, 10 px inset, the same hover and selected
|
|
59
66
|
background, a 32 px avatar, two lines of text, and the trailing check
|
|
60
67
|
pushed to the right edge.
|
|
68
|
+
16. A surface that opens over the workspace holds the keyboard. `Drawer` and
|
|
69
|
+
`ConfirmDialog` trap Tab inside the open panel, close on Escape, and return
|
|
70
|
+
focus to the control that opened them; the screen only names the first
|
|
71
|
+
field with `autoFocus`, and the panel takes focus when it names none.
|
|
72
|
+
17. A refusal is stated where the reader meets it, never folded into a label.
|
|
73
|
+
A disabled `TableAction` carries `reason`, and a refused file carries the
|
|
74
|
+
`FileUpload` refusal line; an action label stays the name of the action.
|
|
61
75
|
|
|
62
76
|
## Screen pattern
|
|
63
77
|
|
|
@@ -90,6 +104,45 @@ still use the smaller Flowdular `Table` and `TableCard` contract from
|
|
|
90
104
|
the TanStack features, row model, header model and cell rendering so every
|
|
91
105
|
screen keeps the same states, widths and actions.
|
|
92
106
|
|
|
107
|
+
Sorting, narrowing and paging are opt-in on the same `Table`. A column that
|
|
108
|
+
declares `value` (the comparable, searchable value behind the rendered cell) is
|
|
109
|
+
sortable once the table carries `sorting`, and is scanned by `globalFilter`; a
|
|
110
|
+
column without it stays presentation only. Sorting is uncontrolled by default
|
|
111
|
+
and reported through `onSortingChange`; pass `sortingState` as well to keep it
|
|
112
|
+
in the screen's own state. `pagination` takes `pageIndex`, `pageSize` and
|
|
113
|
+
`onPageChange` and slices `rows` client-side; add `totalRows` when `rows`
|
|
114
|
+
already holds one page because the module paginated in SQL. The page window is
|
|
115
|
+
taken over the rows that survive `globalFilter`, so a narrowed set can never
|
|
116
|
+
page into emptiness. The table never moves the page on its own: it clamps the
|
|
117
|
+
index when the row set shrinks under it and reports that through `onPageChange`
|
|
118
|
+
after the render commits, so the reader keeps their place when they sort or
|
|
119
|
+
filter. A screen that wants the first page after a sort or a new search term
|
|
120
|
+
resets the index itself.
|
|
121
|
+
|
|
122
|
+
A list the module already sorted, narrowed and cut in SQL passes `mode="server"`
|
|
123
|
+
instead. The table then renders `rows` exactly as they arrive and narrows
|
|
124
|
+
nothing: the headers still toggle and report through `onSortingChange`, the page
|
|
125
|
+
still reports through `onPageChange`, and the screen turns both into its next
|
|
126
|
+
request. `pagination.totalRows` stays optional there. With a total the table
|
|
127
|
+
clamps the page index the same way it does client-side; without one the page is
|
|
128
|
+
a keyset page nobody counted, and `pagination.hasMore` says whether another
|
|
129
|
+
follows, so an empty page with nothing behind it walks the reader back one page
|
|
130
|
+
rather than leaving them on a page that does not exist.
|
|
131
|
+
|
|
132
|
+
A row action that is refused carries `reason` beside `disabled`. The label stays
|
|
133
|
+
the name of the action and the reason becomes the button's accessible
|
|
134
|
+
description, so a reader who cannot see the greyed button still hears why.
|
|
135
|
+
|
|
136
|
+
The pager is a separate `Pagination` in the `TableCard` `after` slot, so the
|
|
137
|
+
screen holds one page index and size that both the table and the pager read.
|
|
138
|
+
It takes the row total the screen already knows (after its own filtering) and
|
|
139
|
+
a `summary(range)` the screen translates; `pageRange` is exported for a screen
|
|
140
|
+
that needs the same arithmetic elsewhere. Default page size is 25. A keyset
|
|
141
|
+
page passes `hasMore` in place of `totalRows`: Previous behaves the same, Next
|
|
142
|
+
stays open exactly while another page follows, and `summary(page)` reads a
|
|
143
|
+
`keysetPage` with the page index, size and first row but no page count, because
|
|
144
|
+
nobody counted the set.
|
|
145
|
+
|
|
93
146
|
Every column declares a semantic CSS `width`. Give the primary record and its
|
|
94
147
|
description the largest share, medium shares to dates and identifiers, and the
|
|
95
148
|
smallest share to counts and lifecycle state. In a table with row actions, data
|
|
@@ -119,39 +172,130 @@ without settings shows a one-line empty state there.
|
|
|
119
172
|
|
|
120
173
|
## Components
|
|
121
174
|
|
|
122
|
-
| Component | Use
|
|
123
|
-
| ------------------ |
|
|
124
|
-
| `Button` | Actions: `variant` primary, secondary (default), ghost, danger; `size` sm, md, lg; `block`
|
|
125
|
-
| `FormField` | Label + control + help or error. Put `ui-input`, `ui-select`, or `ui-textarea` inside
|
|
126
|
-
| `VariableTextarea` | Multiline template field: `value`, `onInput`, `variables` (scope-filtered `VariableDefinition[]`), `sampleValues`, `label`, `name`; a `braces` menu inserts `{{ key }}` and tokens highlight as pills (error pill when unknown). Presentational, never fetches
|
|
127
|
-
| `VariableInput` | Single-line variant of `VariableTextarea` with the same props
|
|
128
|
-
| `VariableSelect` | Native select that stores either a literal option value or one allowed `{{ key }}` token. Takes scope-filtered `variables`, `sampleValues`, literal `options`, `value`, `onInput`, `name`, `label`, and native required/disabled state. It preserves keyboard, validation, accessibility, and `FormData` semantics and never fetches or resolves data
|
|
129
|
-
| `Tag` | Status and metadata: `tone` neutral, success, warning, danger, info, ink; `dot` adds a state dot; `mono`
|
|
130
|
-
| `Kpi` | Stat tile: `label`, `value`, `unit`, `badge`, `note`, `href`, `linkLabel`
|
|
131
|
-
| `Chart` | Token-driven Chart.js wrapper on a client-only canvas: `type` area, bar, line; `data`, `series` (`key`, `label`, `token`), `xKey`, `height`, `title`, `xTickFormatter`; series colors come from `--chart-1..5`; shows an EmptyState for empty or all-zero data
|
|
132
|
-
| `Table` | The one data table, backed by `@octanejs/tanstack-table`: `columns` (`key`, `header`, required `width`, `numeric`, `cell`), `rows`, `rowKey`, `status` idle/loading, `loadingLabel`, `empty` and `emptyFiltered` picked by `filtered`, `actions(row): TableAction[]`, `actionsLabel`, optional stable `actionsWidth` (160 px by default, 280 px for two actions), `onSelect` with `selectedKey`, `caption`; fixed layout and `colgroup` keep columns stable across states |
|
|
133
|
-
| `TableCard` | The record card around `Table`: `title`, `count`, the `head`, `search`, and `filters` head slots, `before` and `after` around the table, `note` with `noteIcon` as the footer
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
175
|
+
| Component | Use |
|
|
176
|
+
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
177
|
+
| `Button` | Actions: `variant` primary, secondary (default), ghost, danger; `size` sm, md, lg; `block` |
|
|
178
|
+
| `FormField` | Label + control + help or error. Put `ui-input`, `ui-select`, or `ui-textarea` inside |
|
|
179
|
+
| `VariableTextarea` | Multiline template field: `value`, `onInput`, `variables` (scope-filtered `VariableDefinition[]`), `sampleValues`, `label`, `name`; a `braces` menu inserts `{{ key }}` and tokens highlight as pills (error pill when unknown). Presentational, never fetches |
|
|
180
|
+
| `VariableInput` | Single-line variant of `VariableTextarea` with the same props |
|
|
181
|
+
| `VariableSelect` | Native select that stores either a literal option value or one allowed `{{ key }}` token. Takes scope-filtered `variables`, `sampleValues`, literal `options`, `value`, `onInput`, `name`, `label`, and native required/disabled state. It preserves keyboard, validation, accessibility, and `FormData` semantics and never fetches or resolves data |
|
|
182
|
+
| `Tag` | Status and metadata: `tone` neutral, success, warning, danger, info, ink; `dot` adds a state dot; `mono` |
|
|
183
|
+
| `Kpi` | Stat tile: `label`, `value`, `unit`, `badge`, `note`, `href`, `linkLabel` |
|
|
184
|
+
| `Chart` | Token-driven Chart.js wrapper on a client-only canvas: `type` area, bar, line; `data`, `series` (`key`, `label`, `token`), `xKey`, `height`, `title`, `xTickFormatter`; series colors come from `--chart-1..5`; shows an EmptyState for empty or all-zero data |
|
|
185
|
+
| `Table` | The one data table, backed by `@octanejs/tanstack-table`: `columns` (`key`, `header`, required `width`, `numeric`, `value`, `cell`), `rows`, `rowKey`, `status` idle/loading, `loadingLabel`, `empty` and `emptyFiltered` picked by `filtered`, `sorting` with `sortingState` and `onSortingChange`, `globalFilter`, `pagination`, `mode` client (default) or server, `actions(row): TableAction[]` (`id`, `label`, `icon`, `tone`, `disabled`, `reason`), `actionsLabel`, optional stable `actionsWidth` (160 px by default, 280 px for two actions), `onSelect` with `selectedKey`, `selection` (`selectedKeys`, `onSelectionChange`, `label`, `rowLabel`, `selectable`, `clearLabel`) with `bulkActions: TableBulkAction[]` (`id`, `label`, `icon`, `tone`, `disabled`, `reason`, `onSelect(keys)`) and `selectionSummary(count)`, `caption`; fixed layout and `colgroup` keep columns stable across states. Selection is a leading checkbox column; the header checkbox toggles the rows on screen and reads mixed while some are selected; the bulk bar sits above the table only while something is selected and its clear button empties the whole set through `onSelectionChange`. The screen owns the selected keys and resets them on a page, sort or filter change |
|
|
186
|
+
| `TableCard` | The record card around `Table`: `title`, `count`, the `head`, `search`, and `filters` head slots, `before` and `after` around the table, `note` with `noteIcon` as the footer |
|
|
187
|
+
| `Pagination` | The pager for the `TableCard` `after` slot: `pageIndex`, `pageSize`, `totalRows` or `hasMore` for a keyset page nobody counted, `onPageChange`, `pageSizes` with `onPageSizeChange` (the type couples the pair, so one cannot arrive without the other), `label`, `previousLabel`, `nextLabel`, `pageSizeLabel`, and `summary(range)` that the screen translates |
|
|
188
|
+
| `Select` | The labelled native select: required `id`, `label`, `options` (`value`, `label`, `disabled`), `value`, `onChange`, `placeholder`, `name` (defaults to `id`), `required`, `disabled`, `autoFocus`, `invalid`, `help`, `error` |
|
|
189
|
+
| `DateField` | Native date or datetime control: required `id`, `label`, ISO `value`, `onChange`, `kind` date (default) or datetime, `locale`, `min`, `max`, `name`, `required`, `disabled`, `invalid`, `help`, `error`, `describedBy` for a message a group around the field owns; the reading beside the input repeats the value in the reader's locale |
|
|
190
|
+
| `DateRangeField` | Two `DateField`s with one answer: required `id`, `legend`, `fromLabel`, `toLabel`, `value` (`from`, `to`), `onChange`, `kind`, `locale`, `min`, `max`, `required`, `disabled`, `reversedMessage`, `help`, `error`; each side bounds the other and a reversed range reports instead of swapping |
|
|
191
|
+
| `DatePicker` | The calendar picker: required `id`, `label`, `openLabel`, `previousMonthLabel`, `nextMonthLabel`, `value` (`from`, `to`), `onChange`, `mode` single (default) or range, `kind`, `locale`, `min`, `max`, `presets` with `presetsLabel`, `fromLabel` and `toLabel` for a range, `name`, `required`, `disabled`, `autoFocus`, `invalid`, `help`, `error`; one popover holds the presets and the month grid |
|
|
192
|
+
| `FileUpload` | The file control: required `id`, `label`, `value`, `onChange`, `refusal(reason, file)`, `chosen(file)`, `clearLabel`, plus `accept`, `maxBytes`, `hint`, `busy` with `busyLabel`, `error`, `name`, `required`, `disabled`, `autoFocus`; it refuses an unaccepted type and a file over `maxBytes` and hands the screen back `null` |
|
|
193
|
+
| `Tabs` | Accessible tablist: required `id`, `items` (`id`, `label`, `disabled`), `active`, `onChange`, `label`; arrows, Home and End move roving focus, an `active` that names no enabled tab selects the first enabled one, and the caller renders the panel |
|
|
194
|
+
| `ToastHost` | Renders the toast queue: `label`, `closeLabel`, optional `store`. Raise toasts with `toasts.success/error/info(message)`; `createToastStore` makes a scoped queue |
|
|
195
|
+
| `PageHeader` | Every view starts with it: `eyebrow`, `title`, `description`; children render as right-side actions |
|
|
196
|
+
| `EmptyState` | `icon`, `title`, children, optional `code` |
|
|
197
|
+
| `Alert` | Inline message: `tone` danger (default), warning, info |
|
|
198
|
+
| `Drawer` | Editor panel over the records: `open`, `title`, `subtitle`, `width` md/lg, `onClose`; traps Tab and restores focus to the opener |
|
|
199
|
+
| `SearchField` | Filter control for a panel head: `value`, `placeholder`, `label`, `onInput` |
|
|
200
|
+
| `CheckGrid` | Grouped multi-select for scopes, tools, and long option sets: `groups` (`label`, `options` of `value`, `label`, `hint`), `value`, `mono`, `disabled`, `onChange` |
|
|
201
|
+
| `ScopeSummary` | Read-only summary of `module.entity.action` scopes: one row per module, one chip per entity with its actions: `scopes`, `labels` (module id to display name) |
|
|
202
|
+
| `SettingRow` | One setting: `label`, `description` (node, one line), `scopeLabel` (small neutral tag), `status` (`ok`, `message` of the last save), children as the control cluster |
|
|
203
|
+
| `Avatar` | Initials from `name`: `square` for organizations, round for people; `large` in profile and account headers |
|
|
204
|
+
| `Switch` | Boolean setting that applies on its own (no form submit): `checked`, `label` as the accessible name, `disabled`, `onChange` |
|
|
205
|
+
| `ConfirmDialog` | One question before an irreversible action: `open`, `title`, children, `confirmLabel`, `tone` danger (default) or primary, `busy`, `onConfirm`, `onCancel`; traps Tab and restores focus to the opener |
|
|
206
|
+
| `Icon` | Stroke icon by `name` from `ICON_PATHS`; `size` 18 default, 16 in controls, 14 in `Button size="sm"`; `strokeWidth` 1.75 default |
|
|
207
|
+
| `BrandMark` | The weave: `size`, `signature` (copper weft, large brand moments only), `tone` brand, current, inverse |
|
|
147
208
|
|
|
148
209
|
`Drawer` takes one child, a `ui-drawer__form` (fields in `ui-drawer__body`, actions in `ui-drawer__foot`) or a plain `ui-drawer__body`; it closes on Escape and on the scrim. `SearchField` carries no visible label, so pass `label` as its accessible name. `FormField` renders `error` in place of `help` and marks it `role="alert"`. `SettingRow` is presentation only: the caller owns the draft value, the save call, and passes the result back as `status`. `ScopeSummary` is the read side of `CheckGrid`; both keep the first-seen module order, and `summarizeScopes` is exported for callers that need the grouping without the markup.
|
|
149
210
|
|
|
211
|
+
`Select`, `DateField` and `DateRangeField` own their own label, so they go
|
|
212
|
+
straight into a form row and not inside a `FormField`. Each requires an `id`:
|
|
213
|
+
it binds the label to the control and keeps the browser's association stable
|
|
214
|
+
across renders, and `name` defaults to it for `FormData`. `invalid` marks the
|
|
215
|
+
control without occupying the message line; `error` does both and replaces
|
|
216
|
+
`help`.
|
|
217
|
+
|
|
218
|
+
A `DateField` holds the ISO value the platform stores and the form submits
|
|
219
|
+
(`YYYY-MM-DD`, or `YYYY-MM-DDTHH:mm` for `kind="datetime"`), because a native
|
|
220
|
+
date input picks its own display format and no page can change it. The reading
|
|
221
|
+
beside the input repeats that value in the reader's locale: pass `locale` from
|
|
222
|
+
`activeLocale()`, and use the exported `formatDateValue` for the same reading
|
|
223
|
+
in a table cell. `DateRangeField` bounds each side by the other, so the picker
|
|
224
|
+
cannot produce a reversed range, and shows `reversedMessage` when one arrives
|
|
225
|
+
from the screen's own state; `dateRangeReversed` is exported for the same check
|
|
226
|
+
before a submit. Its one help or error line describes both inputs through
|
|
227
|
+
`describedBy`, because a fieldset's own `aria-describedby` never reaches the
|
|
228
|
+
controls inside it.
|
|
229
|
+
|
|
230
|
+
`DatePicker` is the calendar over those same native inputs: the trigger opens
|
|
231
|
+
one popover holding the presets and the month grid, and a range is picked in
|
|
232
|
+
that one calendar, first end then second, ordered on the way out so the picker
|
|
233
|
+
can never make a reversed range. `mode="single"` reads and writes `value.from`
|
|
234
|
+
and mirrors it into `to`, because a single day is a range whose ends match and
|
|
235
|
+
one shape keeps one keyboard model. Arrows move by day and week, PageUp and
|
|
236
|
+
PageDown by month, Home and End to the ends of the week, Enter or Space selects,
|
|
237
|
+
and Escape closes the calendar and stops there, so a picker inside a `Drawer`
|
|
238
|
+
never dismisses the drawer with the same keystroke. The four standard windows
|
|
239
|
+
come from `datePresetRange('today' | 'last-7-days' | 'last-30-days' |
|
|
240
|
+
'this-month')`, which the screen pairs with its own translated labels;
|
|
241
|
+
'this-month' is the month so far. With `kind="datetime"` the calendar answers
|
|
242
|
+
about the day only and keeps the time the value already carried, clamping the
|
|
243
|
+
result into `min` and `max`. Use `DateField` for a plain date a reader types,
|
|
244
|
+
`DateRangeField` for two dates in two labelled fields, and `DatePicker` when the
|
|
245
|
+
reader picks from a calendar or takes a window in one click.
|
|
246
|
+
|
|
247
|
+
`FileUpload` owns the two refusals a browser cannot state: a content type the
|
|
248
|
+
`accept` list does not cover, and a file over `maxBytes`. It detects them and
|
|
249
|
+
the screen supplies the words through `refusal(reason, file)`, the same way
|
|
250
|
+
every other primitive takes its copy. A refused file is never handed on: the
|
|
251
|
+
control reports `null`, empties the native list so the same file can be chosen
|
|
252
|
+
again, and shows the refusal in place of the reading. It re-reads the file it
|
|
253
|
+
holds whenever the limits change, so a ceiling that arrives after the drawer
|
|
254
|
+
opened still refuses what it must, and the screen keeps one `File | null` and no
|
|
255
|
+
check of its own. The native input stays the focus stop, so the picker opens
|
|
256
|
+
from the keyboard.
|
|
257
|
+
|
|
258
|
+
`Drawer` and `ConfirmDialog` hold the keyboard while they are open. Tab wraps at
|
|
259
|
+
both ends of the panel, Escape closes, and closing returns focus to the control
|
|
260
|
+
that opened the panel. Focus moves in on open: to the control the screen marked
|
|
261
|
+
`autoFocus` if there is one, otherwise to the first focusable control in the
|
|
262
|
+
panel, otherwise to the panel itself. A drawer whose first field is a `Select`
|
|
263
|
+
marks it `autoFocus`; the screen never moves focus by hand.
|
|
264
|
+
|
|
265
|
+
`Tabs` renders only the tablist. The caller renders the panel and wires it to
|
|
266
|
+
the tab: the tabs are `<id>-tab-<item.id>` and point at `<id>-panel-<item.id>`,
|
|
267
|
+
so a panel is `<div id={id + '-panel-' + active} role="tabpanel"
|
|
268
|
+
aria-labelledby={id + '-tab-' + active}>`. Arrow keys move focus and selection
|
|
269
|
+
over enabled tabs and wrap, Home and End jump to the ends, and only the active
|
|
270
|
+
tab is in the tab order, so Tab leaves the list for the panel.
|
|
271
|
+
|
|
272
|
+
`Toast` is a transient confirmation of something the reader just did, never a
|
|
273
|
+
state a screen must keep showing: a failure that blocks work stays in `Alert`.
|
|
274
|
+
A screen that raises toasts renders one `ToastHost`; the region keeps its place
|
|
275
|
+
in the DOM while empty, because a live region added with its first message is
|
|
276
|
+
not announced. `toasts.success`, `.error` and `.info` raise them from anywhere,
|
|
277
|
+
the queue keeps the four newest and dismisses each after five seconds, and
|
|
278
|
+
`createToastStore` makes a scoped queue for a surface or a test. The entry
|
|
279
|
+
animation follows the global reduced-motion rule in `base.css`.
|
|
280
|
+
|
|
281
|
+
The five states read the same way on every one of these: `Select`, `DateField`,
|
|
282
|
+
`DateRangeField` and `DatePicker` are loading when the screen disables them,
|
|
283
|
+
empty with an empty `value` (and a `placeholder` on `Select`), in error through
|
|
284
|
+
`error` or `invalid`, populated with a value, and denied by not being rendered
|
|
285
|
+
at all. `FileUpload` is loading while `busy`, empty with no `value` and its
|
|
286
|
+
`hint` on the line, in error through `error` or a refusal it made itself,
|
|
287
|
+
populated with the `chosen` reading beside a remove button, and denied the same
|
|
288
|
+
way: the screen does not render it.
|
|
289
|
+
`Table` keeps its own loading row, empty state and filtered-empty message, and
|
|
290
|
+
`Pagination` reads "1 of 1" over an empty set rather than disappearing.
|
|
291
|
+
`ToastHost` has no loading or denied state: it is empty or it carries toasts.
|
|
292
|
+
|
|
150
293
|
Icon names (`packages/ui/src/icons/Icon.tsrx`): `dashboard`, `parties`, `catalog`,
|
|
151
294
|
`user`, `users`, `shield`, `code`, `modules`, `file-text`, `play`, `bot`,
|
|
152
|
-
`flask`, `activity`, `plug`, `search`, `chevron-down`, `
|
|
295
|
+
`flask`, `activity`, `plug`, `search`, `chevron-down`, `chevron-left`,
|
|
296
|
+
`chevron-right`, `chevrons-up-down`, `sort`, `calendar`,
|
|
153
297
|
`plus`, `panel-left`, `check`, `filter`, `download`, `more`, `external`,
|
|
154
|
-
`alert`, `x`, `sign-out`, `refresh`, `help`, `key`, `settings`, `braces`. An unknown name renders
|
|
298
|
+
`alert`, `x`, `sign-out`, `refresh`, `help`, `info`, `key`, `settings`, `braces`. An unknown name renders
|
|
155
299
|
`modules` without a warning; a new icon is one 24x24 stroke path added there.
|
|
156
300
|
|
|
157
301
|
## Classes
|
|
@@ -172,8 +316,8 @@ Icon names (`packages/ui/src/icons/Icon.tsrx`): `dashboard`, `parties`, `catalog
|
|
|
172
316
|
with `__section-head` (`b` title, `small` description) and
|
|
173
317
|
`__section--danger`), `ui-control-row` (a growing control beside one fixed
|
|
174
318
|
button), `ui-choices` (a short wrapping row of `ui-checkbox`), `ui-input`
|
|
175
|
-
(+`--error`), `ui-select
|
|
176
|
-
`ui-label`, `ui-help` (+`--error`)
|
|
319
|
+
(+`--error`), `ui-select` (+`--error`), `ui-textarea` (+`--error`),
|
|
320
|
+
`ui-checkbox`, `ui-label`, `ui-help` (+`--error`)
|
|
177
321
|
- Drawer: `ui-drawer__form` (scrolling body plus pinned footer),
|
|
178
322
|
`ui-drawer__body`, `ui-drawer__foot`
|
|
179
323
|
- Settings rows inside a `ui-card` or a drawer `ui-form__section`, rendered
|
|
@@ -183,7 +327,8 @@ Icon names (`packages/ui/src/icons/Icon.tsrx`): `dashboard`, `parties`, `catalog
|
|
|
183
327
|
- Buttons: `ui-btn` with `--primary`, `--secondary`, `--ghost`, `--danger`,
|
|
184
328
|
`--sm`, `--lg`, `--block`, and `--icon` for an icon-only square button
|
|
185
329
|
(the `Button` component renders all but `--icon`)
|
|
186
|
-
- Bits: `ui-
|
|
330
|
+
- Bits: `ui-visually-hidden` (text only a screen reader reads), `ui-kbd`,
|
|
331
|
+
`ui-dot` (+`--muted`), `ui-note`, `ui-menu` (+`__label`,
|
|
187
332
|
`__section` for a static identity or connection block, `__item`,
|
|
188
333
|
`__item--active`, `__item--danger`, `__sep`), `ui-icon`
|
|
189
334
|
|
|
@@ -194,6 +339,15 @@ Rendered by components, not written by hand: `ui-page-head*`, `ui-search`,
|
|
|
194
339
|
`__note`, `__link`), `ui-avatar` (+`--sq`, `--lg`), `ui-checks` (+`__group`,
|
|
195
340
|
`__head`, `__count`, `__all`, `__items`, `__option`), `ui-scopes` (+`__row`,
|
|
196
341
|
`__chip`), `ui-drawer-slot`, `ui-drawer` (+`__panel`, `__panel--lg`, `__head`,
|
|
342
|
+
`__close`), `ui-table__sort` with `ui-table__sort-icon` (+`--on`, `--asc`),
|
|
343
|
+
`ui-pagination` (+`__summary`, `__size`, `__pages`), `ui-datefield`
|
|
344
|
+
(+`__reading`), `ui-daterange` (+`__row`), `ui-datepicker` (+`__row`,
|
|
345
|
+
`__trigger`, `__scrim`, `__popover`, `__presets`, `__preset`, `__calendar`,
|
|
346
|
+
`__head`, `__month`, `__step`, `__grid`, `__day` (+`--outside`, `--between`,
|
|
347
|
+
`--on`)), `ui-fileupload` (+`__control`, `__input`, `__clear`),
|
|
348
|
+
`ui-table-action` (the wrapper carrying a refused action's title),
|
|
349
|
+
`ui-tabs` (+`__tab`, `__tab--on`),
|
|
350
|
+
`ui-toasts` with `ui-toast` (+`--success`, `--error`, `--info`, `__message`,
|
|
197
351
|
`__close`), `ui-varfield` (+`__control`, `__highlight`, `__input`, `__pill`
|
|
198
352
|
(+`--error`), `__trigger`, `__menu`, `__value`, `__empty`; the overlay layer
|
|
199
353
|
that highlights `{{ key }}` tokens, rendered by `VariableTextarea` and
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
Run Flowdular on your machine, seed a demo workspace, and sign in.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js 22.22.2 or newer
|
|
8
|
+
- pnpm 11.17.0
|
|
9
|
+
- No database server: the platform ships an embedded PostgreSQL and keeps it
|
|
10
|
+
under `.flowdular/data`
|
|
11
|
+
|
|
12
|
+
## Install and check the workspace
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm install
|
|
16
|
+
pnpm flowdular doctor
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`doctor` reports workspace health (configuration, enabled modules, generated
|
|
20
|
+
composition, guardrail files). Add `--json` for a machine-readable envelope.
|
|
21
|
+
|
|
22
|
+
## Seed a local demo
|
|
23
|
+
|
|
24
|
+
`pnpm flowdular setup` opens an interactive wizard. Choose a local demo, configure PostgreSQL, or check the existing configuration. Local initialization requires confirmation and a stopped application.
|
|
25
|
+
|
|
26
|
+
For scripts and CI, `setup quick` is a destructive local reset. It prints its full plan first and
|
|
27
|
+
writes only after a typed confirmation:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm flowdular setup quick # dry run, prints the plan
|
|
31
|
+
pnpm flowdular setup quick --apply --confirm reset-local-auth # resets and seeds
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Stop `pnpm dev` before applying it. Quick setup is blocked outside development
|
|
35
|
+
and test, and must never point at a deployed database.
|
|
36
|
+
|
|
37
|
+
It creates two demo tenants (Operations Demo, Finance Demo) and two logins:
|
|
38
|
+
|
|
39
|
+
| Account | Password | Role |
|
|
40
|
+
| ------------------- | ---------------- | -------------------------- |
|
|
41
|
+
| `admin@example.com` | `Owner!23456789` | Owner of both demo tenants |
|
|
42
|
+
| `user@example.com` | `Member!2345678` | Reduced scope member |
|
|
43
|
+
|
|
44
|
+
## Run the platform
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pnpm dev
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Open `http://localhost:4310`. Vite HMR covers TSRX, TypeScript and styles. The
|
|
51
|
+
launcher keeps tool warnings quiet; use `pnpm dev -- --verbose` for full
|
|
52
|
+
diagnostics. `pnpm dev` runs `module sync` first, so a composition change is
|
|
53
|
+
picked up without a manual step.
|
|
54
|
+
|
|
55
|
+
The first visit opens the `auth.core` sign-in flow. The session lives in an
|
|
56
|
+
HttpOnly cookie and carries the scopes of the selected tenant membership. On a
|
|
57
|
+
clean database the sign-up wizard is available: workspace name plus a unique
|
|
58
|
+
workspace id (the first URL segment, `/{workspace}/{view}`), then the
|
|
59
|
+
administrator account, then an optional email confirmation step. A bookmark
|
|
60
|
+
pointing at another workspace you belong to switches the session on load.
|
|
61
|
+
|
|
62
|
+
`Development` navigation is visible only to tenant owners; server permissions
|
|
63
|
+
stay authoritative either way.
|
|
64
|
+
|
|
65
|
+
## Where local state lives
|
|
66
|
+
|
|
67
|
+
The database and the development vault keys live under `.flowdular/data`. Every
|
|
68
|
+
module shares one embedded PostgreSQL in `.flowdular/data/pglite`, which
|
|
69
|
+
`FD_DATABASE_PGLITE_DIRECTORY` can redirect. Point `FD_DATABASE_ADAPTER` at
|
|
70
|
+
`postgresql` and give it `FD_DATABASE_URL` to run against a real server instead.
|
|
71
|
+
See [configuration.md](configuration.md).
|
|
72
|
+
|
|
73
|
+
## Migrating preserved state from `.octane-erp`
|
|
74
|
+
|
|
75
|
+
`setup migrate-state` is an isolated compatibility bridge for workspaces that
|
|
76
|
+
still hold state under the old directory:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pnpm flowdular setup migrate-state
|
|
80
|
+
# Stop the platform, the sandbox, and anything holding those files open first.
|
|
81
|
+
pnpm flowdular setup migrate-state --apply --confirm migrate-legacy-state
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The dry run lists every source, destination and collision. Apply copies the
|
|
85
|
+
vault key files, refuses symbolic links and existing destination files, verifies
|
|
86
|
+
every copied file and never removes the legacy source directory. SQLite database
|
|
87
|
+
files are reported by name and left in place: the PostgreSQL and PGlite adapters
|
|
88
|
+
cannot read them, so start a fresh workspace on the current adapter; that data
|
|
89
|
+
does not carry over.
|
|
90
|
+
|
|
91
|
+
## Agents in a local workspace
|
|
92
|
+
|
|
93
|
+
`agents.core` ships enabled: reusable agent definitions, an isolated playground
|
|
94
|
+
and durable run history. Enqueue returns once the run is committed, so execution
|
|
95
|
+
continues across navigation, a closed browser or a sign-out. Agents reach
|
|
96
|
+
platform data only through tools registered against approved API endpoints or
|
|
97
|
+
CLI capabilities. The default local provider is a simulation: it calls no
|
|
98
|
+
external model and no network service, so nothing leaves your machine until you
|
|
99
|
+
bind a real provider under Providers.
|
|
100
|
+
|
|
101
|
+
## Landing site
|
|
102
|
+
|
|
103
|
+
The public website lives in [Flowdular/landing](https://github.com/Flowdular/landing): one
|
|
104
|
+
server-rendered page, no session and no module composition, so marketing work
|
|
105
|
+
never reaches the product. The platform serves the workspace itself at `/`.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
git clone https://github.com/Flowdular/landing.git
|
|
109
|
+
cd landing
|
|
110
|
+
pnpm install
|
|
111
|
+
pnpm dev # http://127.0.0.1:4330
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Next
|
|
115
|
+
|
|
116
|
+
- Build a module: [modules.md](modules.md)
|
|
117
|
+
- Build one by chat: [sandbox.md](sandbox.md)
|
|
118
|
+
- Every command: [cli.md](cli.md)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Installing official modules
|
|
2
|
+
|
|
3
|
+
Core is maintained in [Flowdular/flowdular](https://github.com/Flowdular/flowdular).
|
|
4
|
+
[Flowdular/official-modules](https://github.com/Flowdular/official-modules) owns
|
|
5
|
+
expenses, parties and catalog, including their source, reviews and immutable
|
|
6
|
+
release artifacts. The landing is in [Flowdular/landing](https://github.com/Flowdular/landing).
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pnpm flowdular module search expenses
|
|
10
|
+
pnpm flowdular module info expenses.core
|
|
11
|
+
pnpm flowdular module install expenses.core@0.6.1
|
|
12
|
+
pnpm flowdular module install expenses.core@0.6.1 --apply
|
|
13
|
+
pnpm flowdular module enable expenses.core --apply
|
|
14
|
+
pnpm flowdular module validate --locked
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Without `--apply`, installation and updates return a plan and write nothing.
|
|
18
|
+
Installation downloads source into the first configured module root and writes
|
|
19
|
+
`flowdular.modules.lock.json`. It does not install npm dependencies, run downloaded
|
|
20
|
+
scripts, activate modules, grant scopes or touch a database. Enablement links npm
|
|
21
|
+
packages with install lifecycle scripts disabled, generates composition and runs
|
|
22
|
+
its existing scope-grant flow. Review the source before enabling it.
|
|
23
|
+
|
|
24
|
+
`module update <id[@version]> [--apply]` requires an installer-managed module and
|
|
25
|
+
refuses local source changes, removed or edited historical migrations, incompatible
|
|
26
|
+
versions and downgrades. Additional source files count as local edits. Generated
|
|
27
|
+
`dist`, `node_modules` and Git metadata do not. `module validate --locked` requires
|
|
28
|
+
a lock and checks source hashes as well as ordinary manifest/dependency validation.
|
|
29
|
+
|
|
30
|
+
The installer resolves a consistent dependency closure, including diamond
|
|
31
|
+
constraints, within a bounded search budget. Existing workspace/package versions
|
|
32
|
+
are preserved. Registry and runtime validation share semver semantics, including
|
|
33
|
+
pre-1.0 caret ranges. Every module declares `platformApi` as a range
|
|
34
|
+
(`^0.1.0`); the current platform API is `0.1.3` and `module search --compatible`
|
|
35
|
+
filters releases by it. A release that declares `requires` is resolved together
|
|
36
|
+
with the newest compatible release providing each required capability.
|
|
37
|
+
|
|
38
|
+
## Trust and recovery
|
|
39
|
+
|
|
40
|
+
The default catalog is the official repository's `registry/index.json`. HTTPS
|
|
41
|
+
artifact URLs must point to the declared immutable commit in that same repository.
|
|
42
|
+
Downloads have time and size limits. Source bundles are bounded JSON records of
|
|
43
|
+
regular files, validated for path escapes, duplicate names, digest mismatches,
|
|
44
|
+
identity, portable dependencies and stale/missing review evidence. No archive
|
|
45
|
+
extraction or install lifecycle scripts execute during source installation.
|
|
46
|
+
|
|
47
|
+
A checksum does not authenticate an arbitrary publisher. The trust root is the
|
|
48
|
+
configured official repository over HTTPS. An operator can explicitly supply
|
|
49
|
+
`--registry /absolute/path/index.json` for an offline catalog; artifacts must stay
|
|
50
|
+
inside that catalog directory. Third-party remote registries are not supported.
|
|
51
|
+
A review report is an assessment, not a guarantee or a defense against a malicious
|
|
52
|
+
publisher. Official release CI also executes the checks independently.
|
|
53
|
+
|
|
54
|
+
Concurrent installs share an exclusive transaction directory. Recover an
|
|
55
|
+
interrupted operation with `module recover` and `module recover --apply` after
|
|
56
|
+
its owner process has exited. Recovery restores the previous source and module
|
|
57
|
+
lock and refuses to discard conflicting edits. Keep the transaction directory
|
|
58
|
+
when a conflict is reported. Source recovery never rolls back database migrations.
|
|
59
|
+
Installation and updates are host/operator capabilities and do not expand sandbox
|
|
60
|
+
agents' network, filesystem or tool permissions.
|
|
61
|
+
|
|
62
|
+
## SDK publication and consumer checks
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
pnpm release:pack
|
|
66
|
+
pnpm release:smoke
|
|
67
|
+
# Also install and test actual source artifacts from the official repo:
|
|
68
|
+
node scripts/smoke-sdk.mjs release-artifacts/sdk /path/to/official-modules/registry/local-index.json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`release-artifacts/sdk/sdk.json` lists exactly `@flowdular/sdk`, `flowdular`, `create-flowdular` and `@flowdular/sandbox`, with versions, tarballs
|
|
72
|
+
and SHA-256 digests. Publish those tarballs with `npm publish <tarball> --access public`.
|
|
73
|
+
Publish all SDK dependencies before consumers install the starter. The three
|
|
74
|
+
business modules and the landing are not in this npm publication set. Keep release
|
|
75
|
+
artifacts outside runtime state directories; creating both `.flowdular` and legacy
|
|
76
|
+
`.coreloom` state would correctly stop the application.
|
|
77
|
+
|
|
78
|
+
The smoke test creates a separate project and resolves SDK dependencies from
|
|
79
|
+
packed artifacts, without aliases or symlinks into core sources. With a module
|
|
80
|
+
catalog it installs all module source artifacts, enables their source composition,
|
|
81
|
+
checks the lock, typechecks and executes the consumer's tests. Its composition
|
|
82
|
+
check does not grant tenant scopes; auth CLI tests cover that separate boundary.
|
|
83
|
+
|
|
84
|
+
Sandbox examples use `.ai/references/catalog`, generated from a reviewed official
|
|
85
|
+
artifact. `pnpm reference:check` verifies every file against the pinned provenance
|
|
86
|
+
record. Do not edit the generated reference. To update it, build the CLI and run
|
|
87
|
+
`scripts/module-reference.mjs` with `--artifact`, `--sha256`, `--source-commit` and
|
|
88
|
+
`--apply`. Skills and sandbox preparation refer to that offline snapshot.
|
|
89
|
+
|
|
90
|
+
`pnpm release:publish` previews the exact publication set after validating every
|
|
91
|
+
artifact digest. The operator can then use `pnpm release:publish --apply` after npm
|
|
92
|
+
authentication. It skips already-published identical tarballs and stops if a
|
|
93
|
+
version exists with different bytes. No npm publication is performed by packing,
|
|
94
|
+
smoke testing or the default publication preview.
|
|
95
|
+
|
|
96
|
+
The SDK is assembled from private internal workspaces. Import UI from `@flowdular/sdk/ui` and styles from `@flowdular/sdk/ui/styles`; use `@flowdular/sdk/server`, `client`, `contracts` or `modules/<name>` for other surfaces. There is no root SDK barrel, so browser imports do not load server entrypoints. See [npm publication](https://github.com/flowdular/flowdular/blob/main/docs/npm-publication.md).
|