create-flowdular 0.3.2 → 0.4.1
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 +25 -0
- package/agent-template/.agents/skills/integration-adapter/SKILL.md +116 -0
- package/agent-template/.agents/skills/module-new/SKILL.md +17 -2
- package/agent-template/.agents/skills/module-update/SKILL.md +14 -2
- package/agent-template/.agents/skills/release-eject-pr/SKILL.md +23 -26
- package/agent-template/.agents/skills/spec-interview/SKILL.md +16 -0
- package/agent-template/.agents/skills/translations-i18n/SKILL.md +2 -1
- package/agent-template/.agents/skills/ux-design/SKILL.md +4 -4
- package/agent-template/.ai/agents/sandbox/backend-engineer.md +9 -0
- package/agent-template/.ai/blueprints/agentic-module/README.md +15 -0
- package/agent-template/.ai/blueprints/agentic-module/allowed-paths.yaml +14 -0
- package/agent-template/.ai/blueprints/agentic-module/blueprint.json +19 -0
- package/agent-template/.ai/blueprints/agentic-module/gates.yaml +24 -0
- package/agent-template/.ai/blueprints/agentic-module/input.schema.json +18 -0
- package/agent-template/.ai/blueprints/agentic-module/plan.schema.json +35 -0
- package/agent-template/.ai/blueprints/agentic-module/required-files.yaml +28 -0
- package/agent-template/.ai/blueprints/agentic-module/spec-requirements.yaml +33 -0
- package/agent-template/.ai/blueprints/agentic-module/steps.yaml +68 -0
- package/agent-template/.ai/platform-capabilities.md +14 -11
- package/agent-template/.ai/references/catalog/module.json +2 -2
- package/agent-template/.ai/references/catalog/package.json +2 -2
- package/agent-template/.ai/references/catalog/spec/module.yaml +2 -2
- package/agent-template/.ai/references/catalog/src/client/CatalogItemForm.tsrx +4 -4
- package/agent-template/.ai/references/catalog/src/client/CatalogView.tsrx +28 -27
- package/agent-template/.ai/references/catalog.provenance.json +8 -8
- package/agent-template/.ai/rules/flowdular.md +2 -1
- package/agent-template/.ai/skills/README.md +1 -0
- package/agent-template/.ai/skills/agent-tool-design/SKILL.md +25 -0
- package/agent-template/.ai/skills/integration-adapter/SKILL.md +121 -0
- package/agent-template/.ai/skills/module-new/SKILL.md +17 -2
- package/agent-template/.ai/skills/module-update/SKILL.md +14 -2
- package/agent-template/.ai/skills/release-eject-pr/SKILL.md +23 -26
- package/agent-template/.ai/skills/spec-interview/SKILL.md +16 -0
- package/agent-template/.ai/skills/translations-i18n/SKILL.md +2 -1
- package/agent-template/.ai/skills/ux-design/SKILL.md +4 -4
- package/agent-template/.claude/skills/agent-tool-design/SKILL.md +25 -0
- package/agent-template/.claude/skills/integration-adapter/SKILL.md +116 -0
- package/agent-template/.claude/skills/module-new/SKILL.md +17 -2
- package/agent-template/.claude/skills/module-update/SKILL.md +14 -2
- package/agent-template/.claude/skills/release-eject-pr/SKILL.md +23 -26
- package/agent-template/.claude/skills/spec-interview/SKILL.md +16 -0
- package/agent-template/.claude/skills/translations-i18n/SKILL.md +2 -1
- package/agent-template/.claude/skills/ux-design/SKILL.md +4 -4
- package/agent-template/AGENTS.md +2 -1
- package/agent-template/CLAUDE.md +2 -1
- package/agent-template/docs/agent-contract.md +1 -1
- package/agent-template/docs/cli.md +8 -3
- package/agent-template/docs/configuration.md +29 -0
- package/agent-template/docs/design-system.md +112 -13
- package/agent-template/docs/module-distribution.md +10 -3
- package/agent-template/docs/modules.md +38 -1
- package/agent-template/docs/operations.md +2 -0
- package/agent-template/docs/sandbox.md +76 -1
- package/package.json +1 -1
- package/template/default/flowdular.json +2 -0
- package/template/default/modules/example/package.json +1 -1
- package/template/default/modules/example/src/client/NotesView.tsrx +12 -16
- package/template/default/modules/example/tests/module.test.ts +3 -2
- package/template/default/modules/example/translations/pl.json +3 -1
- package/template/default/package.json +1 -1
- package/template/default/platform/package.json +1 -1
- package/template/default/platform/scripts/dev.mjs +16 -1
- package/template/default/platform/src/generated/modules.client.ts +4 -0
- package/template/default/platform/src/generated/modules.server.ts +61 -4
|
@@ -11,7 +11,9 @@ shared primitives, tokens, and the rules for using them.
|
|
|
11
11
|
Variable, IBM Plex Mono) are self-hosted through `@fontsource` packages.
|
|
12
12
|
- `packages/client`: the application shell (sidebar, topbar, command palette,
|
|
13
13
|
dashboard, contribution outlets). Shell-only layout lives in
|
|
14
|
-
`src/shell/shell.css`.
|
|
14
|
+
`src/shell/shell.css`. Below 1200 px the sidebar starts as its icon rail and
|
|
15
|
+
below 960 px the context rail as its strip; either opens over the workspace
|
|
16
|
+
and leaves the stored preference alone (`src/shell/layout.ts`).
|
|
15
17
|
- Modules: compose screens from `@flowdular/sdk/ui`. Module CSS may only add
|
|
16
18
|
module-specific composites built on the tokens (example:
|
|
17
19
|
`modules/agents/src/client/agents.css`).
|
|
@@ -35,9 +37,10 @@ shared primitives, tokens, and the rules for using them.
|
|
|
35
37
|
Numbers in tables and KPIs are tabular (`.num`, `ui-kpi__value`).
|
|
36
38
|
7. Layout containment is owned by the primitives, not by the screen. Children
|
|
37
39
|
of `ui-view`, `ui-two-col`, `ui-grid-2`, and `ui-kpi-grid` are shrinkable
|
|
38
|
-
tracks, long words wrap
|
|
39
|
-
|
|
40
|
-
value can never push the
|
|
40
|
+
tracks, long words wrap (inside a table they end in an ellipsis instead),
|
|
41
|
+
and the workspace never scrolls horizontally. Wide content scrolls inside
|
|
42
|
+
its own container (`ui-table-wrap`), so one long value can never push the
|
|
43
|
+
page sideways.
|
|
41
44
|
8. `Kpi` is a stat tile. Its value is a number or a short state word; addresses,
|
|
42
45
|
identifiers, and paths belong in `note` or a `ui-mono` line.
|
|
43
46
|
9. A screen never splits its width between records and a form. Records own the
|
|
@@ -143,13 +146,9 @@ stays open exactly while another page follows, and `summary(page)` reads a
|
|
|
143
146
|
`keysetPage` with the page index, size and first row but no page count, because
|
|
144
147
|
nobody counted the set.
|
|
145
148
|
|
|
146
|
-
Every column declares a
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
columns normally add up to about 90 percent; the shared 160 px action column
|
|
150
|
-
uses the rest. In a read-only table, data columns add up to 100 percent. The
|
|
151
|
-
table keeps these widths in loading, empty and populated states and scrolls
|
|
152
|
-
horizontally below its minimum readable width.
|
|
149
|
+
Every column declares a `width` and a cell from the typed cells, and a table
|
|
150
|
+
narrower than its columns hides the least important ones before it scrolls.
|
|
151
|
+
The rules are in the Tables section below.
|
|
153
152
|
|
|
154
153
|
The drawer holds one `ui-drawer__form`: fields scroll inside
|
|
155
154
|
`ui-drawer__body`, and the primary action stays pinned in `ui-drawer__foot`.
|
|
@@ -170,6 +169,77 @@ settings are edited in its Drawer under Administration > Modules, as
|
|
|
170
169
|
`SettingRow`s inside the drawer's Settings `ui-form__section`; a module
|
|
171
170
|
without settings shows a one-line empty state there.
|
|
172
171
|
|
|
172
|
+
## Tables
|
|
173
|
+
|
|
174
|
+
A table reads at any width: a cell never breaks a word, a narrow card hides
|
|
175
|
+
the columns that matter least and lists them under the row, and the table
|
|
176
|
+
scrolls only when even its essential columns do not fit.
|
|
177
|
+
|
|
178
|
+
**Cells.** A column's `cell` returns one of the typed cells, so every table
|
|
179
|
+
truncates, titles and aligns the same way. Inside `ui-table` text wraps only at
|
|
180
|
+
spaces; a value without one (an email, an id, an action code, a timestamp)
|
|
181
|
+
stays whole and ends in an ellipsis with the full value as its title.
|
|
182
|
+
|
|
183
|
+
| Cell | Use |
|
|
184
|
+
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
185
|
+
| `CellText` | One line of text: `value`, `strong` for the record's name, `mono` for a code or a path, `wrap` for prose (a description, a message, a list) that wraps at spaces up to two lines, `muted` for a description in the smaller secondary ink, `title` when the hover should say more than the value |
|
|
186
|
+
| `CellStack` | A primary line over a secondary one (a name over its id, a type over its identifier, a state over its note): `primary`, `secondary`, `primaryMono`, `secondaryMono`; a line may be another cell |
|
|
187
|
+
| `CellTime` | A timestamp: `value` as an ISO string, a `YYYY-MM-DD` day or epoch milliseconds, `kind` datetime (default) or date, `timeZone` for a value read in a zone of its own (a schedule's). Compact (`16 Sep, 19:39`, the year only when it is not this year), the hour cycle of the locale, a `<time datetime>` with the full timestamp and seconds as its title |
|
|
188
|
+
| `CellCode` | An identifier in monospace: `value`, `max` (16) past which it keeps its head and tail (`4783b5b2…40d8`), `copy` (true) for the copy button that shows on row hover and focus |
|
|
189
|
+
| `CellTag` | A state or a value with meaning: `label`, `tone`, `dot`, `mono`; the label ends in an ellipsis instead of being cut |
|
|
190
|
+
| `CellNumber` | A count or an amount: `value`, `options` for `Intl.NumberFormat`; a string `value` arrives formatted (`1.2K`, `<$0.01`, `3/5`) and shows as it came; the column declares `numeric` for the right alignment |
|
|
191
|
+
| `CellMuted` | The placeholder for a missing value: "None", "Never"; `title` for the hover reading, such as why the value is missing |
|
|
192
|
+
|
|
193
|
+
**Widths.** `width` stays required. Give a bounded column a px width: a time
|
|
194
|
+
170 px, a status or a tag 120 to 140 px, a number 100 to 140 px, an id or a
|
|
195
|
+
version 120 to 200 px. Give `auto` to the one column that takes the rest,
|
|
196
|
+
usually the record's identity or its description. Percentages still work and
|
|
197
|
+
count as flexible columns.
|
|
198
|
+
|
|
199
|
+
**Priority.** `TableColumn.priority` is 1 (default), 2 or 3:
|
|
200
|
+
|
|
201
|
+
- 1: the identity, the state and the main value. Always visible. The first
|
|
202
|
+
column is always 1.
|
|
203
|
+
- 2: secondary ids, counts and timestamps.
|
|
204
|
+
- 3: details and descriptions.
|
|
205
|
+
|
|
206
|
+
The table wrapper is a size container, and the table computes from the declared
|
|
207
|
+
widths where each column stops fitting: px columns as declared, 200 px per
|
|
208
|
+
flexible column, the selection and action columns, and 28 px for the expand
|
|
209
|
+
button once something hides, rounded up to a 40 px step between 480 and 1600
|
|
210
|
+
px (`table-steps.css`). Columns hide one at a time, priority 3 before 2 and the
|
|
211
|
+
last declared first; each hides below the width it and every column still
|
|
212
|
+
beside it need. The table scrolls only below its minimum, counted from priority
|
|
213
|
+
1 alone at 120 px per flexible column, plus the selection column, the folded
|
|
214
|
+
action column and the expand button. The container queries decide; the only
|
|
215
|
+
measurement is the action column, which grows to its widest row of buttons so a
|
|
216
|
+
longer translation is never cut.
|
|
217
|
+
|
|
218
|
+
**Row expansion.** While any column is hidden, every row starts with an expand
|
|
219
|
+
button (`aria-expanded`). An expanded row lists exactly the columns hidden at
|
|
220
|
+
that width as label and value pairs, rendered by the same cells, so nothing is
|
|
221
|
+
lost and nothing is cut. Widening the card brings the columns back and empties
|
|
222
|
+
the list.
|
|
223
|
+
|
|
224
|
+
**Row actions.** Up to two actions are compact buttons. Two actions fold into
|
|
225
|
+
one More button when the card is narrower than three times the action column
|
|
226
|
+
or than the narrowest table beside it; more than two actions always sit in the
|
|
227
|
+
More menu, and the column then keeps the width of that one button. The menu
|
|
228
|
+
opens over the page, arrows, Home and End move between items, Enter or Space
|
|
229
|
+
runs one, and Escape or Tab closes it and returns focus to the button. A refused
|
|
230
|
+
action stays in the menu, announced as unavailable, with its `reason`.
|
|
231
|
+
|
|
232
|
+
**Clickable rows.** A table with `onSelect` makes its first cell a button, so
|
|
233
|
+
Tab reaches the row and Enter or Space opens it. That column holds text, never
|
|
234
|
+
a control; a `CellCode` there leaves out its copy button.
|
|
235
|
+
|
|
236
|
+
**Headers** stay one line and end in an ellipsis with the label as their title.
|
|
237
|
+
|
|
238
|
+
**Copy and locale.** The expand, collapse, More, copy and copied labels and the
|
|
239
|
+
locale for `CellTime` and `CellNumber` come from `TableLocaleContext`, which the
|
|
240
|
+
shell provides from its own `shell.table.*` bundle. A screen passes none of
|
|
241
|
+
them; outside the shell the English defaults and the host locale apply.
|
|
242
|
+
|
|
173
243
|
## Components
|
|
174
244
|
|
|
175
245
|
| Component | Use |
|
|
@@ -179,10 +249,11 @@ without settings shows a one-line empty state there.
|
|
|
179
249
|
| `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
250
|
| `VariableInput` | Single-line variant of `VariableTextarea` with the same props |
|
|
181
251
|
| `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 |
|
|
252
|
+
| `CellText` | One of the typed table cells (`CellText`, `CellStack`, `CellTime`, `CellCode`, `CellTag`, `CellNumber`, `CellMuted`); see Tables |
|
|
182
253
|
| `Tag` | Status and metadata: `tone` neutral, success, warning, danger, info, ink; `dot` adds a state dot; `mono` |
|
|
183
254
|
| `Kpi` | Stat tile: `label`, `value`, `unit`, `badge`, `note`, `href`, `linkLabel` |
|
|
184
255
|
| `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
|
|
256
|
+
| `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 minimum `actionsWidth` (160 px default, grows to fit its buttons), `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
257
|
| `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
258
|
| `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
259
|
| `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` |
|
|
@@ -191,6 +262,7 @@ without settings shows a one-line empty state there.
|
|
|
191
262
|
| `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
263
|
| `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
264
|
| `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 |
|
|
265
|
+
| `SortableList` | Reorderable list: required `id`, `label`, `items` (`id`, `label`), `onReorder(ids)`, `handleLabel(item)`, `instructions`, `announce(event)`, `renderItem(item, index)`, `disabled`; a grip per item drags with a pointer or picks up, moves and drops from the keyboard, with a polite live region |
|
|
194
266
|
| `ToastHost` | Renders the toast queue: `label`, `closeLabel`, optional `store`. Raise toasts with `toasts.success/error/info(message)`; `createToastStore` makes a scoped queue |
|
|
195
267
|
| `PageHeader` | Every view starts with it: `eyebrow`, `title`, `description`; children render as right-side actions |
|
|
196
268
|
| `EmptyState` | `icon`, `title`, children, optional `code` |
|
|
@@ -269,6 +341,20 @@ aria-labelledby={id + '-tab-' + active}>`. Arrow keys move focus and selection
|
|
|
269
341
|
over enabled tabs and wrap, Home and End jump to the ends, and only the active
|
|
270
342
|
tab is in the tab order, so Tab leaves the list for the panel.
|
|
271
343
|
|
|
344
|
+
`SortableList` renders an ordered list whose order the screen owns: it never
|
|
345
|
+
reorders `items` itself and calls `onReorder` once per committed drop with the
|
|
346
|
+
whole new order, never for a cancel or an order that did not change. Each item
|
|
347
|
+
has a grip button named by `handleLabel` and described by `instructions`. From
|
|
348
|
+
the keyboard, Space or Enter picks the item up, ArrowUp and ArrowDown move it,
|
|
349
|
+
Space or Enter drops it, and Escape or moving focus away puts it back; focus
|
|
350
|
+
stays on the moved grip. With a pointer, the grip drags the item, a line marks
|
|
351
|
+
where it lands, releasing commits, and a cancelled pointer or Escape puts it
|
|
352
|
+
back. Every step reaches a polite live region through `announce`, which gets
|
|
353
|
+
the kind (`lifted`, `moved`, `dropped`, `cancelled`), the item label and its
|
|
354
|
+
position of the total, so the screen supplies the words. `disabled` makes every
|
|
355
|
+
grip inert, which is the loading and denied reading; an empty `items` renders an
|
|
356
|
+
empty list, so the screen shows its own empty state instead.
|
|
357
|
+
|
|
272
358
|
`Toast` is a transient confirmation of something the reader just did, never a
|
|
273
359
|
state a screen must keep showing: a failure that blocks work stays in `Alert`.
|
|
274
360
|
A screen that raises toasts renders one `ToastHost`; the region keeps its place
|
|
@@ -295,7 +381,7 @@ Icon names (`packages/ui/src/icons/Icon.tsrx`): `dashboard`, `parties`, `catalog
|
|
|
295
381
|
`flask`, `activity`, `plug`, `search`, `chevron-down`, `chevron-left`,
|
|
296
382
|
`chevron-right`, `chevrons-up-down`, `sort`, `calendar`,
|
|
297
383
|
`plus`, `panel-left`, `check`, `filter`, `download`, `more`, `external`,
|
|
298
|
-
`alert`, `x`, `sign-out`, `refresh`, `help`, `info`, `key`, `settings`, `braces`. An unknown name renders
|
|
384
|
+
`alert`, `x`, `sign-out`, `refresh`, `help`, `info`, `key`, `settings`, `braces`, `copy`. An unknown name renders
|
|
299
385
|
`modules` without a warning; a new icon is one 24x24 stroke path added there.
|
|
300
386
|
|
|
301
387
|
## Classes
|
|
@@ -346,7 +432,20 @@ Rendered by components, not written by hand: `ui-page-head*`, `ui-search`,
|
|
|
346
432
|
`__head`, `__month`, `__step`, `__grid`, `__day` (+`--outside`, `--between`,
|
|
347
433
|
`--on`)), `ui-fileupload` (+`__control`, `__input`, `__clear`),
|
|
348
434
|
`ui-table-action` (the wrapper carrying a refused action's title),
|
|
435
|
+
`ui-table__lead` with `ui-table__toggle` and `ui-table__open` (the first cell's
|
|
436
|
+
expand and open buttons), `ui-table__hide-*` (a column hidden below that width),
|
|
437
|
+
`ui-table__reveal-*` (the expand button, details list and detail shown below
|
|
438
|
+
that width), `ui-table--fold-*` (the action fold step),
|
|
439
|
+
`ui-table__placeholder-body` (the loading and empty row content),
|
|
440
|
+
`ui-table__details` (+`-list`), `ui-table__detail`,
|
|
441
|
+
`ui-table-more` (+`--always`), `ui-table-menu` (+`__scrim`, `__label`),
|
|
442
|
+
`ui-cell-text` (+`--strong`, `--mono`, `--muted`, `--wrap`), `ui-cell-stack`
|
|
443
|
+
(+`__primary`, `__secondary`, `__line--mono`), `ui-cell-time`, `ui-cell-code`
|
|
444
|
+
(+`__value`, `__copy`, `__copy--done`), `ui-cell-number`, `ui-cell-muted`,
|
|
445
|
+
`ui-tag__label`,
|
|
349
446
|
`ui-tabs` (+`__tab`, `__tab--on`),
|
|
447
|
+
`ui-sortable` (+`__item`, `__item--lifted`, `__item--dragging`,
|
|
448
|
+
`__item--drop-before`, `__item--drop-after`, `__handle`, `__body`),
|
|
350
449
|
`ui-toasts` with `ui-toast` (+`--success`, `--error`, `--info`, `__message`,
|
|
351
450
|
`__close`), `ui-varfield` (+`__control`, `__highlight`, `__input`, `__pill`
|
|
352
451
|
(+`--error`), `__trigger`, `__menu`, `__value`, `__empty`; the overlay layer
|
|
@@ -8,8 +8,8 @@ release artifacts. The landing is in [Flowdular/landing](https://github.com/Flow
|
|
|
8
8
|
```sh
|
|
9
9
|
pnpm flowdular module search expenses
|
|
10
10
|
pnpm flowdular module info expenses.core
|
|
11
|
-
pnpm flowdular module install expenses.core@0.8.
|
|
12
|
-
pnpm flowdular module install expenses.core@0.8.
|
|
11
|
+
pnpm flowdular module install expenses.core@0.8.1
|
|
12
|
+
pnpm flowdular module install expenses.core@0.8.1 --apply
|
|
13
13
|
pnpm flowdular module enable expenses.core --apply
|
|
14
14
|
pnpm flowdular module validate --locked
|
|
15
15
|
```
|
|
@@ -31,7 +31,7 @@ The installer resolves a consistent dependency closure, including diamond
|
|
|
31
31
|
constraints, within a bounded search budget. Existing workspace/package versions
|
|
32
32
|
are preserved. Registry and runtime validation share semver semantics, including
|
|
33
33
|
pre-1.0 caret ranges. Every module declares `platformApi` as a range
|
|
34
|
-
(`^0.1.0`); the current platform API is `
|
|
34
|
+
(`^0.1.0`); the current platform API is `PLATFORM_API_VERSION` in `packages/contracts/src/index.ts` and `module search --compatible`
|
|
35
35
|
filters releases by it. A release that declares `requires` is resolved together
|
|
36
36
|
with the newest compatible release providing each required capability.
|
|
37
37
|
|
|
@@ -93,4 +93,11 @@ authentication. It skips already-published identical tarballs and stops if a
|
|
|
93
93
|
version exists with different bytes. No npm publication is performed by packing,
|
|
94
94
|
smoke testing or the default publication preview.
|
|
95
95
|
|
|
96
|
+
The `SDK consumer smoke` workflow (`.github/workflows/sdk-release.yml`, job
|
|
97
|
+
`consumer`) runs the pack and the smoke on every pull request and push to main
|
|
98
|
+
that touches `packages`, `modules` or `scripts`. It never publishes: its token
|
|
99
|
+
can only read the repository, and the packed tarballs are kept as an Actions
|
|
100
|
+
artifact only after a merge to main. `pnpm verify` is not repeated there, the
|
|
101
|
+
CI workflow owns it.
|
|
102
|
+
|
|
96
103
|
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).
|
|
@@ -39,6 +39,18 @@ spec instead of scanning the repository. Every section is optional and a
|
|
|
39
39
|
`agentTools[]`: `id`, `permission`, `description`, `risk`.
|
|
40
40
|
- `outOfScope[]` records what is deliberately not built; `decisions[]` records
|
|
41
41
|
each interview question, its answer and whether a user or a default decided.
|
|
42
|
+
- `research`: `adapter` (`model-native`, `searxng`, `firecrawl`, `connector`
|
|
43
|
+
or `recorded`),
|
|
44
|
+
`allowDomains`, `denyDomains`, `monthlyQueryBudget`, and `evidenceOwner`, the
|
|
45
|
+
entity whose records evidence attaches to through `research.core`.
|
|
46
|
+
- `adapters[]`: `id` (prefixed with the module id), `direction` (`source` or
|
|
47
|
+
`sink`), `connector` and `operation` (a `connectors.core` definition and
|
|
48
|
+
operation key), `port` (an import port id for a source, a list export id for a
|
|
49
|
+
sink), `schedule` (a five-field cron or `null`), `mapping[]` (`from`, `to`,
|
|
50
|
+
`transform` of `rename`, `constant`, `format` or `lookup`, `value`) and
|
|
51
|
+
`recorded` (`adapters/<name>.recorded.json`).
|
|
52
|
+
- `templates[]`: `id`, `title`, `inputEntity`, `format` (`pdf` or `docx`) and
|
|
53
|
+
`body` (`templates/<name>.md`).
|
|
42
54
|
|
|
43
55
|
Validation is more than the schema: an action permission must exist in
|
|
44
56
|
`permissions`, every `entity` must name an entity, screen `columns` and
|
|
@@ -53,6 +65,19 @@ at least one entity (`SPEC_ACTION_PERMISSION_UNKNOWN`, `SPEC_ENTITY_UNKNOWN`,
|
|
|
53
65
|
`SPEC_DUPLICATE_ID`). A client without a list screen, or a stored entity with no
|
|
54
66
|
tenant-unique field, is a warning.
|
|
55
67
|
|
|
68
|
+
The three optional sections have checks of their own. `research.evidenceOwner`
|
|
69
|
+
and `templates[].inputEntity` must name an entity (`SPEC_ENTITY_UNKNOWN`). An
|
|
70
|
+
adapter id must start with the module id (`SPEC_ADAPTER_ID_NAMESPACE`); a source
|
|
71
|
+
adapter's `port` must belong to this module or a declared dependency
|
|
72
|
+
(`SPEC_ADAPTER_PORT_UNKNOWN`); `schedule` must be a cron `automations.core`
|
|
73
|
+
accepts: minute, hour, day of month, month and day of week, each `*`, a number,
|
|
74
|
+
a three letter month or weekday name, a list, a range or a step
|
|
75
|
+
(`SPEC_ADAPTER_SCHEDULE_INVALID`); and a mapping needs `from` unless it is a
|
|
76
|
+
`constant` and `value` unless it is a `rename` (`SPEC_ADAPTER_MAPPING_INVALID`).
|
|
77
|
+
`recorded` stays optional here; a sandbox session refuses a live adapter in its
|
|
78
|
+
own `spec-schema` gate (`SANDBOX_LIVE_ADAPTER_REFUSED`, see
|
|
79
|
+
[sandbox.md](sandbox.md)).
|
|
80
|
+
|
|
56
81
|
### 2. Scaffold
|
|
57
82
|
|
|
58
83
|
```bash
|
|
@@ -82,6 +107,15 @@ lifecycle field is set by the service), and the columns of the first `list`
|
|
|
82
107
|
screen become the table view. Further entities are the implementing agent's
|
|
83
108
|
work.
|
|
84
109
|
|
|
110
|
+
The optional sections scaffold declarations and fixtures, nothing that runs:
|
|
111
|
+
`src/research.ts` and `research-fixtures.json` (one example query and page in
|
|
112
|
+
the shape the recorded research adapter reads) for `research`; per adapter one
|
|
113
|
+
`src/adapters/<name>.ts` (the id without the module id, dots turned into
|
|
114
|
+
hyphens) and one recorded fixture stub at the declared `recorded` path, else
|
|
115
|
+
`adapters/<name>.recorded.json`; and one `templates/<name>.md` per template
|
|
116
|
+
body, with `templates` added to the package `files`. Implementing them follows the `module-new` and `integration-adapter`
|
|
117
|
+
skills.
|
|
118
|
+
|
|
85
119
|
Files are written through the workspace Prettier, so the format gate passes
|
|
86
120
|
without a rewrite. A directory that already holds `spec/module.yaml` or
|
|
87
121
|
`translations/**` is extended, not rejected, and a failed run leaves nothing
|
|
@@ -119,7 +153,10 @@ pnpm --filter @flowdular/module-<dir> test
|
|
|
119
153
|
`module validate` checks more than the schema: `platform.server` requires
|
|
120
154
|
`src/platform.ts` and a `./platform` export, `platform.client` requires
|
|
121
155
|
`src/client/index.ts` and a `./client` export, and every declared locale needs a
|
|
122
|
-
`translations/<locale>.json` with the same key set as the others (an error).
|
|
156
|
+
`translations/<locale>.json` with the same key set as the others (an error). A
|
|
157
|
+
plural family such as `count.one`/`count.other` in `en` and
|
|
158
|
+
`count.one`/`count.few`/`count.many`/`count.other` in `pl` counts as one key,
|
|
159
|
+
and each locale must carry the categories its plural rules select.
|
|
123
160
|
`module.json` version drift against `specVersion`, or a locale missing from
|
|
124
161
|
`flowdular.json`, is reported as a warning.
|
|
125
162
|
|
|
@@ -356,6 +356,8 @@ variable falls back to the default and logs one warning about it.
|
|
|
356
356
|
## Production checklist
|
|
357
357
|
|
|
358
358
|
- `pnpm verify` and `pnpm build` pass on the commit being shipped.
|
|
359
|
+
The container image only builds; it does not run the tests again, so a tag
|
|
360
|
+
must point at a commit whose CI verify passed.
|
|
359
361
|
- `FD_ENV=production`, `FD_DATABASE_ADAPTER=postgresql`, separate
|
|
360
362
|
`FD_DATABASE_URL` and `FD_DATABASE_MIGRATOR_URL` roles, neither `SUPERUSER`
|
|
361
363
|
nor `BYPASSRLS` on the runtime role.
|
|
@@ -65,6 +65,79 @@ so a workspace can change them.
|
|
|
65
65
|
When the work is done, eject it into `modules/` and enable it, or open a pull
|
|
66
66
|
request with the gate evidence attached.
|
|
67
67
|
|
|
68
|
+
## Sample data and recorded adapters
|
|
69
|
+
|
|
70
|
+
A session can carry a sample of the data the module will really see: attach a
|
|
71
|
+
CSV, JSON or plain text file (`.csv`, `.json`, `.txt`) to the brief or the
|
|
72
|
+
composer, within the attachment limits (5 MB per file, 10 per session). The file
|
|
73
|
+
stays in the session directory, is never sent to the connected application, and
|
|
74
|
+
is deleted with the session.
|
|
75
|
+
|
|
76
|
+
Every turn reads the sample through the read-only sandbox tool `sample-data`.
|
|
77
|
+
Without input it lists each sample file with its columns, row count and a
|
|
78
|
+
parsed preview of the first 20 rows; `{ "name": "customers.csv" }` previews one
|
|
79
|
+
file. CSV is parsed with quoted fields and a detected `,`, `;` or tab delimiter,
|
|
80
|
+
JSON from a top-level array or the first array inside an object, text as lines.
|
|
81
|
+
A value is cut at 200 characters and a row at 40 columns; one preview stays
|
|
82
|
+
under 32 KB and the listing under 64 KB, listing a file that does not fit
|
|
83
|
+
without its rows. The BYOK driver offers the tool to the model. The local
|
|
84
|
+
`claude` and `codex` drivers have no tool channel, so the same listing is
|
|
85
|
+
written to `reference/sample-data.json` for the turn.
|
|
86
|
+
|
|
87
|
+
The backend engineer derives the module's fixtures from the sample:
|
|
88
|
+
`tests/fixtures/*.json` for the tests and `preview/seed.json` for the preview. It
|
|
89
|
+
keeps the shape and replaces real names, contacts and identifiers with invented
|
|
90
|
+
values, because fixtures ship with the module. The role may write `preview/**`,
|
|
91
|
+
`src/preview.ts`, `research-fixtures.json` and `adapters/**`.
|
|
92
|
+
|
|
93
|
+
### Seeding the preview
|
|
94
|
+
|
|
95
|
+
When a draft module has both `preview/seed.json` (at most 1 MB) and
|
|
96
|
+
`src/preview.ts` exporting `seed`, the preview calls it once the generation has
|
|
97
|
+
started:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
export async function seed(context: {
|
|
101
|
+
readonly tenantId: string; // the preview workspace
|
|
102
|
+
readonly accountId: string; // the preview account
|
|
103
|
+
readonly data: unknown; // parsed preview/seed.json
|
|
104
|
+
readonly databases: DatabaseProvider; // the preview's own provider
|
|
105
|
+
}): Promise<void>;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`seed()` writes through the module's own repository, as the server composition
|
|
109
|
+
does. The sandbox keeps a hash of both files in the session data directory and
|
|
110
|
+
calls `seed()` again only when one of them changes, so it must be idempotent
|
|
111
|
+
(upsert by the natural key). A failure is reported as the preview error of that
|
|
112
|
+
module and never stops the preview. The preview does not seed through an
|
|
113
|
+
`import.ports.v1` port: only `import.core` can read its port registry (the
|
|
114
|
+
public capability registers ports), a port write needs a full principal and a
|
|
115
|
+
job, and most drafts do not compose `import.core`.
|
|
116
|
+
|
|
117
|
+
### Recorded adapters
|
|
118
|
+
|
|
119
|
+
A draft spec with a `research` section previews through `research.core`. The
|
|
120
|
+
preview composes it from the platform modules ahead of the drafts and holds
|
|
121
|
+
`research.core.adapter` at `recorded`, the adapter chain at the recorded adapter
|
|
122
|
+
alone (`searchOrder` at `recorded` with `recordedEnabled` on, `fetchOrder` at
|
|
123
|
+
`direct`) and `research.core.recordedFixturesPath` at the absolute path of the
|
|
124
|
+
module's `research-fixtures.json` for every workspace. A chain holding the
|
|
125
|
+
recorded adapter reads pages from the same file before it looks at the fetch
|
|
126
|
+
order, so nothing reaches the network. Changing any of them answers
|
|
127
|
+
`409 SANDBOX_LIVE_ADAPTER_REFUSED`. When several drafts declare research, the
|
|
128
|
+
first one in session order supplies the path. An entry of the spec's `adapters`
|
|
129
|
+
section reads the fixture its `recorded` field names, by convention
|
|
130
|
+
`adapters/<id>.recorded.json`.
|
|
131
|
+
|
|
132
|
+
A session declares only recorded adapters; an owner connects a live search or
|
|
133
|
+
connector instance after delivery. When a draft spec sets `research.adapter` to
|
|
134
|
+
`model-native`, `searxng`, `firecrawl` or `connector`, or lists an adapter
|
|
135
|
+
without `recorded`, the `spec-schema` gate fails with
|
|
136
|
+
`SANDBOX_LIVE_ADAPTER_REFUSED` and names the file and field, so the turn goes
|
|
137
|
+
back to its specialist and delivery stops. The preview refuses to compose such a
|
|
138
|
+
session with the same code before a worker starts. A `research` section without
|
|
139
|
+
`adapter` is previewed on the recorded fixtures.
|
|
140
|
+
|
|
68
141
|
## Deliver as a pull request
|
|
69
142
|
|
|
70
143
|
The eject route (`POST /sandbox/api/sessions/:id/eject`) takes `target:
|
|
@@ -81,7 +154,9 @@ request time (`delivery/configuration.ts`): `targets`, `default`,
|
|
|
81
154
|
`maxChangedFiles` and `git` with `remote`
|
|
82
155
|
(`origin`), `repository` (`owner/name`, derived from the remote when null),
|
|
83
156
|
`baseBranch` (`main`), `branchPrefix` (`sandbox`), `provider` (`github` or
|
|
84
|
-
`none`), `mode` (`auto`, `direct` or `fork`), `forkOwner` and
|
|
157
|
+
`none`), `mode` (`auto`, `direct` or `fork`), `forkOwner`, `reviewers` and
|
|
158
|
+
`labels` (added to the pull request after creation, default
|
|
159
|
+
`sandbox-delivery`; a label the repository lacks never fails a delivery).
|
|
85
160
|
|
|
86
161
|
Operator settings live in the sandbox configuration
|
|
87
162
|
(`.flowdular/sandbox/config.json`, `GitHubDeliveryConfiguration` in
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"system.core",
|
|
13
13
|
"auth.core",
|
|
14
14
|
"access.core",
|
|
15
|
+
"adapters.core",
|
|
15
16
|
"reports.core",
|
|
16
17
|
"metering.core",
|
|
17
18
|
"agents.core",
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
"import.core",
|
|
27
28
|
"notifications.core",
|
|
28
29
|
"profile.core",
|
|
30
|
+
"research.core",
|
|
29
31
|
"sandbox.core",
|
|
30
32
|
"search.core",
|
|
31
33
|
"users.core",
|
|
@@ -2,6 +2,9 @@ import { useEffect, useMemo } from 'octane';
|
|
|
2
2
|
import {
|
|
3
3
|
Alert,
|
|
4
4
|
Button,
|
|
5
|
+
CellStack,
|
|
6
|
+
CellText,
|
|
7
|
+
CellTime,
|
|
5
8
|
Drawer,
|
|
6
9
|
FormField,
|
|
7
10
|
Icon,
|
|
@@ -31,20 +34,18 @@ function columns(): readonly TableColumn<Note>[] {
|
|
|
31
34
|
{
|
|
32
35
|
key: 'note',
|
|
33
36
|
header: t('example.table.column.note'),
|
|
34
|
-
width: '
|
|
35
|
-
cell: (note) =>
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
width: 'auto',
|
|
38
|
+
cell: (note) =>
|
|
39
|
+
<CellStack
|
|
40
|
+
primary={note.title}
|
|
41
|
+
secondary={<CellText value={note.body} muted />}
|
|
42
|
+
/>,
|
|
39
43
|
},
|
|
40
44
|
{
|
|
41
45
|
key: 'created',
|
|
42
46
|
header: t('example.table.column.created'),
|
|
43
|
-
width: '
|
|
44
|
-
cell: (note) =>
|
|
45
|
-
dateStyle: 'medium',
|
|
46
|
-
timeStyle: 'short',
|
|
47
|
-
}).format(new Date(note.createdAt)),
|
|
47
|
+
width: '170px',
|
|
48
|
+
cell: (note) => <CellTime value={note.createdAt} />,
|
|
48
49
|
},
|
|
49
50
|
];
|
|
50
51
|
}
|
|
@@ -152,12 +153,7 @@ export function NotesView(props: NotesViewProps) @{
|
|
|
152
153
|
}
|
|
153
154
|
<TableCard
|
|
154
155
|
title={t('example.table.title')}
|
|
155
|
-
count={t(
|
|
156
|
-
notes.length === 1
|
|
157
|
-
? 'example.table.count.one'
|
|
158
|
-
: 'example.table.count.other',
|
|
159
|
-
{ count: notes.length },
|
|
160
|
-
)}
|
|
156
|
+
count={t('example.table.count', { count: notes.length })}
|
|
161
157
|
search={<SearchField
|
|
162
158
|
value={query}
|
|
163
159
|
placeholder={t('example.table.search.placeholder')}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { translationKeys } from '@flowdular/sdk/contracts';
|
|
3
4
|
import manifest from '../module.json' with { type: 'json' };
|
|
4
5
|
import translationsEn from '../translations/en.json' with { type: 'json' };
|
|
5
6
|
import translationsPl from '../translations/pl.json' with { type: 'json' };
|
|
@@ -28,8 +29,8 @@ describe('example.core manifest', () => {
|
|
|
28
29
|
|
|
29
30
|
describe('example.core translations', () => {
|
|
30
31
|
it('ships the same key set in every declared locale', () => {
|
|
31
|
-
expect(
|
|
32
|
-
|
|
32
|
+
expect(translationKeys(translationsPl)).toEqual(
|
|
33
|
+
translationKeys(translationsEn),
|
|
33
34
|
);
|
|
34
35
|
expect(manifest.locales).toEqual(['en', 'pl']);
|
|
35
36
|
});
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"page.action.new": "Nowa notatka",
|
|
10
10
|
"table.title": "Notatki",
|
|
11
11
|
"table.count.one": "{count} notatka",
|
|
12
|
-
"table.count.
|
|
12
|
+
"table.count.few": "{count} notatki",
|
|
13
|
+
"table.count.many": "{count} notatek",
|
|
14
|
+
"table.count.other": "{count} notatki",
|
|
13
15
|
"table.column.note": "Notatka",
|
|
14
16
|
"table.column.created": "Utworzono",
|
|
15
17
|
"table.search.placeholder": "Szukaj notatek",
|
|
@@ -10,6 +10,14 @@ import {
|
|
|
10
10
|
} from '@flowdular/sdk/dev-console';
|
|
11
11
|
|
|
12
12
|
const appRoot = resolve(fileURLToPath(new URL('..', import.meta.url)));
|
|
13
|
+
/* `pnpm dev -- --port 4396 --host 0.0.0.0` overrides vite.config.ts, so a
|
|
14
|
+
second application runs beside the first one. */
|
|
15
|
+
const flag = (name) => {
|
|
16
|
+
const index = process.argv.indexOf(name);
|
|
17
|
+
return index === -1 ? undefined : process.argv[index + 1];
|
|
18
|
+
};
|
|
19
|
+
const port = Number(flag('--port'));
|
|
20
|
+
const host = flag('--host');
|
|
13
21
|
const verbose =
|
|
14
22
|
process.argv.includes('--verbose') ||
|
|
15
23
|
process.argv.includes('-v') ||
|
|
@@ -24,6 +32,11 @@ try {
|
|
|
24
32
|
configFile: resolve(appRoot, 'vite.config.ts'),
|
|
25
33
|
customLogger: createOctaneLogger(verbose, color),
|
|
26
34
|
clearScreen: false,
|
|
35
|
+
...(Number.isInteger(port) && port > 0
|
|
36
|
+
? { server: { port, strictPort: true, ...(host ? { host } : {}) } }
|
|
37
|
+
: host
|
|
38
|
+
? { server: { host } }
|
|
39
|
+
: {}),
|
|
27
40
|
});
|
|
28
41
|
await server.listen();
|
|
29
42
|
} catch (error) {
|
|
@@ -37,7 +50,9 @@ printReady({
|
|
|
37
50
|
lines: [
|
|
38
51
|
[
|
|
39
52
|
'local',
|
|
40
|
-
server.resolvedUrls?.local?.[0] ??
|
|
53
|
+
server.resolvedUrls?.local?.[0] ??
|
|
54
|
+
server.resolvedUrls?.network?.[0] ??
|
|
55
|
+
'the address vite.config.ts sets',
|
|
41
56
|
'info',
|
|
42
57
|
],
|
|
43
58
|
['diagnostics', verbose ? 'verbose' : 'quiet · use --verbose', 'muted'],
|