toga-ai 1.0.165 → 1.0.166

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.
@@ -0,0 +1,11 @@
1
+ # toga-blox (TOGa Blox) — 2.0 knowledge
2
+
3
+ | Doc | Summary | Files |
4
+ |-----|---------|-------|
5
+ | [TOGa Blox (toga-blox) Architecture](architecture.md) | `toga-blox` (npm package **`@agilant/toga-blox`**) is the **shared React + TypeScript component / template / utility library** reused across the 2.0 frontends — | toga-blox/package.json, toga-blox/src/components/index.ts, toga-blox/src/api/index.ts, toga-blox/src/templates/index.ts |
6
+ | [AdvancedSelect (virtualized single/multi select)](features/advanced-select.md) | `AdvancedSelect<T>` is a **fully controlled, virtualized** single/multi select built from scratch (not react-select) on **`@tanstack/react-virtual`**, for large | toga-blox/src/components/AdvancedSelect/AdvancedSelect.tsx, toga-blox/src/components/AdvancedSelect/AdvancedSelect.types.ts, toga-blox/src/components/AdvancedSelect/AdvancedSelect.module.css |
7
+ | [API client (axios wrapper, auth, table-data fetchers)](features/api-client.md) | `src/api/` is a thin **axios** wrapper that standardizes the **2.0 API envelope**, manages auth (Bearer + refresh), serializes complex query options, and provid | toga-blox/src/api/index.ts, toga-blox/src/api/axiosInstance.ts, toga-blox/src/api/apiFunctions.ts, toga-blox/src/api/auth.ts, toga-blox/src/api/genericApi.ts, toga-blox/src/api/types.ts, toga-blox/src/api/tableData |
8
+ | [BaseInput (react-hook-form field factory)](features/base-input.md) | `BaseInput` is a **form-field factory** driven by react-hook-form. | toga-blox/src/components/BaseInput/BaseInput.tsx, toga-blox/src/components/BaseInput/BaseInput.types.ts, toga-blox/src/components/BaseInput/BaseInput.module.css, toga-blox/src/components/BaseInput/components |
9
+ | [Primary Table templates (server/client, sizing, virtualization)](features/primary-table-templates.md) | `src/templates/PrimaryTable/` is the **production, wired-up table** built on the [Table component](table.md). | toga-blox/src/templates/PrimaryTable/PrimaryTable.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableServerTemplate.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableClientTemplate.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableHeaderCell.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableBodyCell.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableRow.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableExpandableRow.tsx, toga-blox/src/templates/PrimaryTable/types.ts |
10
+ | [TableRecordModal (record-detail modal shell)](features/table-record-modal.md) | `TableRecordModal` is a **generic, presentational modal shell** for showing a single table record (row) in detail — typically opened from a table row click. | toga-blox/src/components/TableRecordModal/TableRecordModal.tsx, toga-blox/src/components/TableRecordModal/index.ts, toga-blox/src/components/TableRecordModal/tableRecordModal.module.css |
11
+ | [Table component (cells, action cells, filters & sorts, hooks, theming)](features/table.md) | The `Table` component (`src/components/Table/`) is the **TanStack Table v8** building block behind the [Primary Table templates](primary-table-templates.md). | toga-blox/src/components/Table/index.ts, toga-blox/src/components/Table/types.ts, toga-blox/src/components/Table/utils/buildTanstackColumns.tsx, toga-blox/src/components/Table/utils/resolveCellType.tsx, toga-blox/src/components/Table/components/cellTypes, toga-blox/src/components/Table/components/actionCells, toga-blox/src/components/Table/components/columnFiltersAndSorts, toga-blox/src/components/Table/hooks, toga-blox/src/components/Table/themeConfig |
@@ -0,0 +1,123 @@
1
+ ---
2
+ title: TOGa Blox (toga-blox) Architecture
3
+ framework: "2.0"
4
+ repo: toga-blox
5
+ project: TOGa Blox
6
+ client: shared
7
+ type: architecture
8
+ status: active
9
+ updated: 2026-06-23
10
+ owners: [apeterson]
11
+ files:
12
+ - toga-blox/package.json
13
+ - toga-blox/src/components/index.ts
14
+ - toga-blox/src/api/index.ts
15
+ - toga-blox/src/templates/index.ts
16
+ related:
17
+ - features/table.md
18
+ - features/primary-table-templates.md
19
+ - features/table-record-modal.md
20
+ - features/base-input.md
21
+ - features/advanced-select.md
22
+ - features/api-client.md
23
+ ---
24
+
25
+ ## Summary
26
+
27
+ `toga-blox` (npm package **`@agilant/toga-blox`**) is the **shared React + TypeScript
28
+ component / template / utility library** reused across the 2.0 frontends — `toga2-view`,
29
+ `toga2-supply`, `toga25-supply`, `toga2-commerce`, and `toga2-hub`. It is **not a PHP
30
+ framework repo**: it has no `_underscore` backend code. It is filed under `2.0/` because
31
+ every consumer is a 2.0 app, but structurally it is a standalone front-end package
32
+ (`dependsOn: []`).
33
+
34
+ It ships: presentational + data-aware **components** (`src/components/`), opinionated
35
+ **templates** that wire components into ready-to-use screens (`src/templates/`, e.g.
36
+ `PrimaryTable`, `Login`, `AddNotes`), a thin **API client** over axios + React Query
37
+ (`src/api/`, `src/reactQuery/`), and **utilities** (`src/utils/`). It builds with `tsc`
38
+ to `dist/` (not bundled), develops via Storybook, and tests with Vitest.
39
+
40
+ **Critical rules:**
41
+ - **The API client is inert until initialized.** A consumer **must** call
42
+ `createAxiosInstance({ baseURL, onLogout })` then `setAxiosInstance(instance)` once at
43
+ startup, or every `apiGet/Post/Put/Delete` throws "no axios instance registered". See
44
+ [api-client](features/api-client.md).
45
+ - **`BaseInput` requires a react-hook-form `<FormProvider>` ancestor** — it calls
46
+ `useFormContext()` and throws outside one. See [base-input](features/base-input.md).
47
+ - This is a **published library** — a breaking change to any exported prop/type ripples to
48
+ all five consuming apps at their next `@agilant/toga-blox` bump. Treat exported component
49
+ props and `src/api` signatures as a public contract; bump and publish via the beta flow.
50
+ - Styling is **CSS Modules + CSS custom properties** (`var(--toga-*)`, `var(--baseInput-*)`,
51
+ etc.) injected by `themeConfig/ThemeProvider`. Skins/themes are selected by prop, colors by
52
+ CSS variables on `document.documentElement` — never hardcode palette values in components.
53
+
54
+ ## Package & build
55
+
56
+ | Aspect | Detail |
57
+ |---|---|
58
+ | Name / type | `@agilant/toga-blox`, published to npm (beta tag for pre-release) |
59
+ | Build | `npm run build` → `tsc` emit to `dist/` + copy `*.scss/*.css/*.module.css` + assets, then `scripts/fix-esm-imports.mjs` and `scripts/fix-asset-imports.mjs` post-process import paths |
60
+ | Dev | `npm run dev` (nodemon rebuild) and `npm run blox` (Storybook on :6006) |
61
+ | Test | `npm run test` (Vitest) |
62
+ | CSS | `npm run build-css` (Tailwind → `dist/main.css`); components also use CSS Modules |
63
+
64
+ Because the build is `tsc`-emit (not a bundler), **post-build scripts rewrite import
65
+ specifiers** so the emitted ESM resolves correctly and asset imports point at `dist/assets`.
66
+ A consumer installs the package and imports from `@agilant/toga-blox`.
67
+
68
+ ## Layout
69
+
70
+ ```
71
+ src/
72
+ ├── components/ # the component catalog (Table, BaseInput, AdvancedSelect, TableRecordModal, …)
73
+ │ └── index.ts # public barrel — what consumers import
74
+ ├── templates/ # opinionated, wired-up screens
75
+ │ ├── PrimaryTable/ # the production table template (server/client variants)
76
+ │ ├── Login/
77
+ │ └── AddNotes/
78
+ ├── api/ # axios client, auth, generic CRUD, table-data fetchers
79
+ ├── reactQuery/ # useApiQuery / useApiMutation wrappers + query helpers
80
+ └── utils/ # formatters (currency, phone), endpoint resolution, etc.
81
+ ```
82
+
83
+ **Components vs. templates.** A *component* (e.g. `BaseInput`, `AdvancedSelect`, the Table
84
+ primitives) is a reusable building block. A *template* (e.g. `PrimaryTable`) composes
85
+ components + hooks + the API client into a drop-in feature. The Table story is split across
86
+ both: low-level pieces and cell/filter components live in `src/components/Table/`, while the
87
+ fully-wired table lives in `src/templates/PrimaryTable/`. See
88
+ [primary-table-templates](features/primary-table-templates.md).
89
+
90
+ ## Data layer
91
+
92
+ `src/api/` is a thin wrapper over **axios** that standardizes the **2.0 API envelope**
93
+ (`{ isSuccess, status, error, messages, data, meta, transactionId, ... }`), auto-injects a
94
+ Bearer token + a per-request `transactionId`, and retries once on 401 via a refresh-token
95
+ flow before logging out. `src/reactQuery/` wraps those helpers as `useApiQuery` /
96
+ `useApiMutation`. Table data fetching (`src/api/tableData/`) serializes table view metadata
97
+ (fields, joins, sorts, filters, pagination) into the API's query-string option format. Full
98
+ detail in [api-client](features/api-client.md).
99
+
100
+ ## Theming
101
+
102
+ `src/components/Table/themeConfig/ThemeProvider.tsx` wraps the app and injects a client
103
+ `ClientTheme` (`{ id, name, vars }`) as CSS custom properties on `document.documentElement`.
104
+ Components reference those variables in CVA variants and CSS Modules. Table supports named
105
+ **skins** (`supply`, `desk`, `supply-nested`, `supply-modal-table`) selected by the `skin`
106
+ prop. Form inputs theme via `--baseInput-*` variables.
107
+
108
+ ## Consumers
109
+
110
+ `toga2-view`, `toga2-supply`, `toga25-supply`, `toga2-commerce`, `toga2-hub`. These apps
111
+ talk to the 2.0 API (`api2` over `_underscore`); `toga-blox` is the shared presentation +
112
+ client layer they all build on. (These consumers are not registered as `dependsOn` here —
113
+ the dependency runs the other way, app → library; record it on the consuming app's doc when
114
+ relevant.)
115
+
116
+ ## Related docs
117
+
118
+ - [Table component](features/table.md) — cell types, action cells, filters/sorts, hooks, utils, theming.
119
+ - [Primary Table templates](features/primary-table-templates.md) — the wired server/client table + measure-and-freeze sizing.
120
+ - [TableRecordModal](features/table-record-modal.md) — the record-detail modal shell.
121
+ - [BaseInput](features/base-input.md) — the react-hook-form field factory.
122
+ - [AdvancedSelect](features/advanced-select.md) — virtualized single/multi select.
123
+ - [API client](features/api-client.md) — axios wrapper, auth, table-data fetchers.
@@ -0,0 +1,73 @@
1
+ ---
2
+ title: AdvancedSelect (virtualized single/multi select)
3
+ framework: "2.0"
4
+ repo: toga-blox
5
+ project: TOGa Blox
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-23
10
+ owners: [apeterson]
11
+ files:
12
+ - toga-blox/src/components/AdvancedSelect/AdvancedSelect.tsx
13
+ - toga-blox/src/components/AdvancedSelect/AdvancedSelect.types.ts
14
+ - toga-blox/src/components/AdvancedSelect/AdvancedSelect.module.css
15
+ related:
16
+ - ../architecture.md
17
+ - base-input.md
18
+ ---
19
+
20
+ ## Summary
21
+
22
+ `AdvancedSelect<T>` is a **fully controlled, virtualized** single/multi select built from
23
+ scratch (not react-select) on **`@tanstack/react-virtual`**, for large option sets. Features:
24
+ client- or server-side search, infinite pagination, keyboard navigation, chips (multi),
25
+ initials avatars, secondary labels, and error/dirty display. It is the select used by
26
+ [`BaseInput`](base-input.md) for `advancedSelect` / `advancedMultiSelect`.
27
+
28
+ ## API (discriminated union by `mode`)
29
+
30
+ `AdvancedSelectProps<T> = AdvancedSelectSingleProps<T> | AdvancedSelectMultiProps<T>`.
31
+
32
+ Common (`AdvancedSelectBaseProps<T>`): `data: T[]`, `getOptionValue/getOptionLabel`
33
+ (required), `getOptionSecondaryLabel?`, `showAvatar?` + `getOptionAvatarText?`,
34
+ `findSpecificValue?` (async server search), `searchDebounceMs?` (350), `searchMinChars?` (3),
35
+ `hasMorePages?` + `isFetchingMore?` + `fetchMoreData?` (infinite scroll), `rowHeight?` (36),
36
+ `maxVisibleRows?` (8), `overscan?` (6), `errorMessage?`, `isFieldDirty?`, `isFieldError?`,
37
+ `disabled?`, `valueKey?`, `onOpen/onClose/onClearInput?`.
38
+
39
+ - **single**: `value: T | null | undefined`, `onSelect: (item: T | null, valueKey?) => void`.
40
+ - **multi**: `value: T[]`, `onSelect: (items: T[], valueKey?) => void`.
41
+
42
+ `AdvancedSelectOption<T> = { value, label, secondaryLabel?, item }`.
43
+
44
+ ## Behavior
45
+
46
+ - **Data source**: `searchResults ?? data`. Mapped to internal options; selected values held
47
+ in a `Set` for O(1) lookup. **Selected items always float to the top** (stable order).
48
+ - **Client search** (no `findSpecificValue`): filters `data` by `label.includes(query)`.
49
+ **Server search**: when `input.length >= searchMinChars`, debounced call, previous request
50
+ **aborted via `AbortController`**; pagination is disabled while search results are active.
51
+ - **Infinite scroll**: when the virtualizer reaches the last row and `hasMorePages`, calls
52
+ `fetchMoreData()` (with a shimmer loader row).
53
+ - **Keyboard**: ArrowUp/Down move `activeIndex`, Enter selects, Escape closes, Backspace on an
54
+ empty multi input removes the last chip. Single-select closes on pick and clears the search
55
+ text; multi keeps the menu open and re-focuses the input.
56
+ - **Controlled**: selection state lives in the parent via `value`/`onSelect`; only UI state
57
+ (open, input text, activeIndex, searchResults) is local.
58
+ - CSS Modules + CSS variables (shares `--baseInput*` dirty/error styling); shimmer keyframe for
59
+ the loading row.
60
+
61
+ ## Gotchas
62
+
63
+ - **`renderOption` prop is declared but unused** — option rendering is hardcoded (primary +
64
+ secondary label, check/checkbox, avatar). Treat custom rendering as not yet supported.
65
+ - Single-select with a `secondaryLabel` and menu closed shows a styled two-line value; the
66
+ native input collapses to width 0 (`.inputCollapsed`).
67
+ - Selected-float-to-top is **not** configurable.
68
+ - `mode` is checked at runtime (`mode === "multi"`) — mismatched value/onSelect types can slip
69
+ past TypeScript's union if cast.
70
+ - No built-in required validation or full ARIA listbox/option roles (aria-labels on buttons only).
71
+
72
+ ## Change history
73
+ - 2026-06-23 — Documented AdvancedSelect: virtualization, client/server search with abort, infinite scroll, keyboard nav, single/multi modes, and the unused `renderOption` caveat (apeterson).
@@ -0,0 +1,100 @@
1
+ ---
2
+ title: API client (axios wrapper, auth, table-data fetchers)
3
+ framework: "2.0"
4
+ repo: toga-blox
5
+ project: TOGa Blox
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-23
10
+ owners: [apeterson]
11
+ files:
12
+ - toga-blox/src/api/index.ts
13
+ - toga-blox/src/api/axiosInstance.ts
14
+ - toga-blox/src/api/apiFunctions.ts
15
+ - toga-blox/src/api/auth.ts
16
+ - toga-blox/src/api/genericApi.ts
17
+ - toga-blox/src/api/types.ts
18
+ - toga-blox/src/api/tableData
19
+ related:
20
+ - ../architecture.md
21
+ - table.md
22
+ ---
23
+
24
+ ## Summary
25
+
26
+ `src/api/` is a thin **axios** wrapper that standardizes the **2.0 API envelope**, manages
27
+ auth (Bearer + refresh), serializes complex query options, and provides CRUD + table-data
28
+ fetchers. `src/reactQuery/` wraps it as `useApiQuery` / `useApiMutation`.
29
+
30
+ **The client is inert until initialized**: a consumer must call
31
+ `createAxiosInstance({ baseURL, onLogout? })` then `setAxiosInstance(instance)` once at
32
+ startup. Otherwise `apiGet/Post/Put/Delete` throw "no axios instance registered".
33
+
34
+ ## Core helpers (`apiFunctions.ts`)
35
+
36
+ `apiGet<T>(route, options?, params?)`, `apiPost<T>(route, data, options?, params?)`,
37
+ `apiPut<T>(...)`, `apiDelete<T>(...)`. `options` is serialized to a query string via
38
+ `assembleOptions()` (reactQuery/queryHelpers): `fields` → comma list (nested → dot notation),
39
+ `where` → `(field:op:value,AND/OR,…)` (ops: like/contains/starts/ends/in/between/ge/le/gt/lt/
40
+ eq/ne/excludes), `join`/`ojoin` → `Table:a=b;…`, `sort` → `+field,-field`. `apiDelete` allows
41
+ status < 500.
42
+
43
+ ## Axios instance (`axiosInstance.ts`)
44
+
45
+ `createAxiosInstance({ baseURL, onLogout })`: `Content-Type: application/json`, `timeout
46
+ 180000` (3 min). Interceptors: (1) inject a per-request `transactionId` UUID; (2) inject
47
+ `Authorization: Bearer <accessToken>` from localStorage, falling back to `fetchPublicToken()`;
48
+ (3) on **401**, call `refreshAccessToken()` and retry once (`_retry` guard) — on failure call
49
+ `onLogout` (default `performLogout()`, which clears tokens and redirects to `/`).
50
+
51
+ ## Auth (`auth.ts`)
52
+
53
+ `performLogout`, `fetchPublicToken(baseURL)` (`POST /auth/public`), `refreshAccessToken(baseURL)`
54
+ (`POST /auth/refresh` with refresh-token bearer), `validateEmailDomain(email)`,
55
+ `submitLoginPassword(email, password, fields)` (`POST /auth/login`),
56
+ `handleClientAuthentication(uuid)` (encrypted-uuid SAML/SSO). Tokens stored in localStorage
57
+ (`accessToken`, `refreshToken`, `user`). `AuthResponse = { data: { tokens{access,refresh},
58
+ user{firstName,lastName,uuid} }, messages? }`.
59
+
60
+ ## Generic CRUD (`genericApi.ts`)
61
+
62
+ `getData(slug, fields?)` (auto-paginates at recordsPerPage 1000, returns flattened array;
63
+ slug → camelCase response key), `saveData(slug, payload, returnPayloadDepth?)`,
64
+ `updateData(slug, uuid, payload, returnPayloadDepth?)`, `deleteData(slug, uuid)`,
65
+ `deleteAddress(uuid)`, `fetchCoreParameters()`. `returnPayloadDepth` appends `?depth=-1` for
66
+ nested data.
67
+
68
+ ## Table data (`tableData/`)
69
+
70
+ `getDataTableData(tableViewMeta, additionalData?, extraFields?, searchParams?, options?)` —
71
+ builds the request from `DataTableMetaProp`: fields (incl. hyperlink/imageUrl), pagination
72
+ (URL `${slug}_page` / `${slug}_recordsPerPage`), sorting (`${slug}_sort[i]=±field`), filtering
73
+ (`${slug}_${field}_<op>` → `where.and[]`), joins (`table@alias` syntax for duplicate tables),
74
+ plus `additionalData`, `_status`, and `apiWhereClause` — all AND-joined.
75
+ `getDataTableMeta(slug)` (`GET /table-views/meta`), `getPageMeta(page, app)`,
76
+ `getColumnFieldFilterOptions(recordRoute, options?, params?)`.
77
+
78
+ ## Envelope & types (`types.ts`)
79
+
80
+ `ApiResponse<T> = { transactionId, timestamp, authority, audience{client,user,app}, isSuccess,
81
+ status, error, messages[], meta{depth}, data: T }`. Other types: `Options`, `OptionsType`,
82
+ `TableViewField` (slug vs `tableViewField` DB path, `type`, `isVisible/Editable/Sortable/
83
+ Filterable`, `hyperlinkField`, `imageUrlField`, `sticky`, `precision`, …), `DataTableMetaProp`,
84
+ `DataTableMetaFormatted`, `SamlData`. Base URL resolution via
85
+ `utils/getEndpointFromEnvironment.ts` (env `VITE_API` / `VITE_API_<HOST>`; pattern
86
+ `api.<env>.togahub.com`).
87
+
88
+ ## Gotchas
89
+
90
+ - **Must `setAxiosInstance` before use** — otherwise every call throws.
91
+ - Always check `response.isSuccess` before reading `data`; `data` keys are **camelCased slugs**
92
+ (`/sales-orders` → `data.salesOrders`).
93
+ - `TableViewField.slug` (URL-safe) ≠ `tableViewField` (DB column path); `getDataTableData` maps
94
+ slug → DB field.
95
+ - Only **401** auto-retries; other network errors need React Query `retry`.
96
+ - 401 refresh needs a `refreshToken` in localStorage, else it logs out (hard redirect to `/`).
97
+ - `transactionId` is injected automatically (request tracing); `depth=-1` returns hydrated nesting.
98
+
99
+ ## Change history
100
+ - 2026-06-23 — Documented the API client: axios init contract, auth/refresh interceptors, query-option serialization, generic CRUD, table-data fetchers, and the 2.0 response envelope (apeterson).
@@ -0,0 +1,70 @@
1
+ ---
2
+ title: BaseInput (react-hook-form field factory)
3
+ framework: "2.0"
4
+ repo: toga-blox
5
+ project: TOGa Blox
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-23
10
+ owners: [apeterson]
11
+ files:
12
+ - toga-blox/src/components/BaseInput/BaseInput.tsx
13
+ - toga-blox/src/components/BaseInput/BaseInput.types.ts
14
+ - toga-blox/src/components/BaseInput/BaseInput.module.css
15
+ - toga-blox/src/components/BaseInput/components
16
+ related:
17
+ - ../architecture.md
18
+ - advanced-select.md
19
+ ---
20
+
21
+ ## Summary
22
+
23
+ `BaseInput` is a **form-field factory** driven by react-hook-form. Given a `Field` config
24
+ object it renders the right input sub-component by `inputType` and wires validation, error
25
+ display, and dirty-state styling through `useFormContext()`. **It must be rendered inside a
26
+ react-hook-form `<FormProvider>`** or it throws.
27
+
28
+ ## API
29
+
30
+ Exported default: `BaseInput: React.FC<BaseInputTypes>`. Two config surfaces:
31
+ - **`field?: Field`** (BaseInput.types.ts) — the per-field config: `uuid`, `valueKey`,
32
+ `inputType`, `inputLabel`, `placeholder`, `isRequired`, `showRequiredIndicator`,
33
+ `characterLimit`, select data (`data`, `getOptionValue/Label/SecondaryLabel`,
34
+ `fetchMoreData`, `findSpecificValue`, `showAvatar`), etc.
35
+ - **`BaseInputTypes`** top-level props — many duplicate `Field` keys (act as
36
+ fallbacks/overrides) plus styling-class props and toggle-specific props
37
+ (`toggleTextPosition`, `toggleActiveLabel`, etc.). `valueKey` is the one required prop.
38
+
39
+ ## Input types (switch on `inputType`)
40
+
41
+ `text` → `BaseTextInput` (icon, `hasTypeCheck` number/currency regex, phone/currency
42
+ formatters), `textArea` → `BaseTextareaInput` (char counter, max-height 5lh), `toggle` →
43
+ `BaseToggle` (wraps `ToggleButton`), `checkbox` → `BaseCheckbox` (SVG check), `radio` →
44
+ `BaseRadioInput` (SVG circles; options are `{value,label}[]`), `disabledSelect` →
45
+ `DisabledSelect` (read-only + lock icon), `advancedSelect` / `advancedMultiSelect` →
46
+ [`AdvancedSelect`](advanced-select.md) (single/multi). Legacy `BaseSelect`/`BaseMultiSelect`
47
+ (react-select) exist but are **not** used by the factory.
48
+
49
+ ## Validation / state
50
+
51
+ - Validation via react-hook-form `Controller` rules: required → "Required field."; text
52
+ `hasTypeCheck` patterns: number `/^[0-9]*\.?[0-9]*$/`, currency
53
+ `/^(\d+|\d{1,3}(,\d{3})+)(\.\d{1,2})?$/`.
54
+ - Errors/dirty read from `formState.errors` / `dirtyFields`; UUID-scoped when
55
+ `requireUuidField`, else keyed by `valueKey`. `getNestedValue` walks dotted paths.
56
+ - `BaseErrorMessage` renders the message; dirty fields get `--baseInputDirty-*` border, errors
57
+ `--baseInputError-*`. CSS Modules + CSS variables (`--baseInput-*`); shared sub-components in
58
+ `components/` (`BaseErrorMessage`, `CharacterLimitMessage`, `Option`, `SingleValue`,
59
+ `DropDownIndicator`, `MultiValueRemove`).
60
+
61
+ ## Gotchas
62
+
63
+ - **Requires `<FormProvider>`** (uses `useFormContext()`).
64
+ - `Field` keys and top-level props overlap; `Field` generally wins — don't set both expecting
65
+ the prop to override.
66
+ - Currency formats **on blur**; phone formats **on keystroke**.
67
+ - Missing `aria-required` / `aria-invalid` / `aria-describedby` — labels + visual asterisk only.
68
+
69
+ ## Change history
70
+ - 2026-06-23 — Documented BaseInput as a react-hook-form field factory: input-type switch, validation/dirty handling, and the FormProvider requirement (apeterson).
@@ -0,0 +1,86 @@
1
+ ---
2
+ title: Primary Table templates (server/client, sizing, virtualization)
3
+ framework: "2.0"
4
+ repo: toga-blox
5
+ project: TOGa Blox
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-23
10
+ owners: [apeterson]
11
+ files:
12
+ - toga-blox/src/templates/PrimaryTable/PrimaryTable.tsx
13
+ - toga-blox/src/templates/PrimaryTable/PrimaryTableServerTemplate.tsx
14
+ - toga-blox/src/templates/PrimaryTable/PrimaryTableClientTemplate.tsx
15
+ - toga-blox/src/templates/PrimaryTable/PrimaryTableHeaderCell.tsx
16
+ - toga-blox/src/templates/PrimaryTable/PrimaryTableBodyCell.tsx
17
+ - toga-blox/src/templates/PrimaryTable/PrimaryTableRow.tsx
18
+ - toga-blox/src/templates/PrimaryTable/PrimaryTableExpandableRow.tsx
19
+ - toga-blox/src/templates/PrimaryTable/types.ts
20
+ related:
21
+ - ../architecture.md
22
+ - table.md
23
+ ---
24
+
25
+ ## Summary
26
+
27
+ `src/templates/PrimaryTable/` is the **production, wired-up table** built on the
28
+ [Table component](table.md). Three entry points:
29
+
30
+ - **`PrimaryTable`** — pure render component. Takes an already-built TanStack `table`
31
+ instance + `tableViewFieldMap` and renders header, body (virtualized), and pagination.
32
+ - **`PrimaryTableServerTemplate`** — wraps `useTableSetup()`; **consumer owns** sorting,
33
+ filtering, and pagination state (passes `handleSortingChange`, `handleColumnFiltersChange`,
34
+ etc.). Use for server-side data.
35
+ - **`PrimaryTableClientTemplate`** — wraps `useTableSetup()`; **owns state internally**
36
+ (sorting/filtering/pagination), `initialPageSize` default 15. Use for in-memory data.
37
+
38
+ > Note: although referred to as "primary table templates **within Table**," the files live
39
+ > at `src/templates/PrimaryTable/`, not under `src/components/Table/`.
40
+
41
+ ## Key props (`PrimaryTableProps`, types.ts)
42
+
43
+ `table`, `tableViewFieldMap`, `skin` ("supply" | "desk" | "supply-nested" |
44
+ "supply-modal-table"), `onRowClick`, `paginationMeta` + `handlePageChange` +
45
+ `handleRecordsPerPageChange`, `isFetching`, `dirtyRows`, `activeRowUuid`, `tableSettings`
46
+ ({ isPaginationEnabled, isInfiniteScrollingEnabled }), `rowHeight`, `containerHeight`
47
+ (default 750), `fetchNextPage` + `hasNextPage` (infinite scroll), `renderExpandedContent`,
48
+ `headerSpan` (multi-row header), `showColumnBorders`, `resizableColumns`, `shrinkColumns`,
49
+ `minColumnWidth` (number | per-column map).
50
+
51
+ ## Column sizing: measure-and-freeze
52
+
53
+ Constants in `PrimaryTable.tsx`: `DEFAULT_MIN_COLUMN_WIDTH = 80`, `ABSOLUTE_MIN_COLUMN_WIDTH
54
+ = 40`, `MIN_RESIZE_WIDTH = 40` (and `MIN_COLUMN_WIDTH = 120` from `buildTanstackColumns`).
55
+
56
+ 1. **Measure** — on first paint, measure header + widest body cell content per column
57
+ (`measureCellContentWidth`), store in `frozenWidths`; per-column floors in `contentFloors`.
58
+ 2. **Freeze** — apply `table-layout: fixed` + a `<colgroup>` of frozen pixel widths so columns
59
+ don't reflow while virtualizing. Resets when the leaf column set changes. Empty tables stay
60
+ on auto-layout.
61
+ 3. **Shrink-to-fit** — when `shrinkColumns` (modal tables), scale widths to container width
62
+ via `fitColumnsToContainer`, respecting `minColumnWidth` → `contentFloors` →
63
+ `DEFAULT_MIN_COLUMN_WIDTH`, clamped to `ABSOLUTE_MIN_COLUMN_WIDTH`; ResizeObserver re-fits.
64
+ 4. **Resize** — when `resizableColumns`, a header drag handle mutates `frozenWidths` only
65
+ (visual, local, resets on column change).
66
+
67
+ ## Rows & virtualization
68
+
69
+ `PrimaryTableHeaderCell` (label + `SortIcon` with multi-sort index + filter UI dispatch by
70
+ field type + resize handle + sticky edge divider), `PrimaryTableBodyCell` (`flexRender`,
71
+ sticky styling, action-column detection via `meta.isActionColumn`), `PrimaryTableRow` (dirty/
72
+ active/hover states; click handler ignores text-selection; renders expanded content row),
73
+ `PrimaryTableExpandableRow` (adds chevron toggle). Body uses a virtualizer with top/bottom
74
+ spacer rows computed from the virtualizer item `.start`/`.end` minus `scrollMargin`. The
75
+ `supply-modal-table` skin **disables virtualization** (renders all rows, min-height 300px).
76
+ Sticky columns use a `--sticky-offset` CSS variable summed from neighboring column widths.
77
+
78
+ ## Gotchas
79
+
80
+ - Frozen widths measure only **mounted** rows; virtualized tables size from the first paint set.
81
+ - `headerSpan` adds a separate top row (`colSpan`); only the leaf header row maps to colgroup.
82
+ - Resize is non-persistent (local state, resets when columns change).
83
+
84
+ ## Change history
85
+ - 2026-06-23 — Documented the three Primary Table entry points, measure-and-freeze sizing, virtualization, and sticky columns (apeterson).
86
+ - 2026-06-22 — Added `shrinkColumns` / `minColumnWidth` shrink-to-fit for modal tables; freeze logic uses natural content width (apeterson).
@@ -0,0 +1,69 @@
1
+ ---
2
+ title: TableRecordModal (record-detail modal shell)
3
+ framework: "2.0"
4
+ repo: toga-blox
5
+ project: TOGa Blox
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-23
10
+ owners: [apeterson]
11
+ files:
12
+ - toga-blox/src/components/TableRecordModal/TableRecordModal.tsx
13
+ - toga-blox/src/components/TableRecordModal/index.ts
14
+ - toga-blox/src/components/TableRecordModal/tableRecordModal.module.css
15
+ related:
16
+ - ../architecture.md
17
+ - table.md
18
+ ---
19
+
20
+ ## Summary
21
+
22
+ `TableRecordModal` is a **generic, presentational modal shell** for showing a single table
23
+ record (row) in detail — typically opened from a table row click. It handles **positioning,
24
+ animation, backdrop, and lifecycle only**; the consumer supplies the record's field rendering
25
+ via `children`. It does **not** render fields, manage edit/view modes, or provide
26
+ accessibility/focus management — all of that is the consumer's responsibility.
27
+
28
+ ## API (props)
29
+
30
+ `{ isOpen, onClose, position?, children, slideUpModalClassName?, variant?, isMaximized?,
31
+ style? }`:
32
+
33
+ - `isOpen: boolean`, `onClose: () => void` — controlled visibility.
34
+ - `position?: "right" | "left" | "bottom" | "top" | "center"` (default `"right"`) — each has
35
+ its own slide animation and backdrop opacity (right/left 0.2, top/bottom 0.4, center 0.5 +
36
+ blur).
37
+ - `children: ReactNode | ((onClose) => ReactElement)` — static content **or** a render-prop
38
+ function that receives `onClose` (avoids prop-drilling the close handler).
39
+ - `variant?: "default" | "expandable"` + `isMaximized?` — expandable variant toggles width
40
+ between minimized (57%) and full (100%); `isMaximized` is ignored for other variants.
41
+ - `slideUpModalClassName?` — arbitrary extra class merged onto the wrapper (misnomer: not
42
+ slide-up-specific). `style?` — inline override.
43
+
44
+ ## Behavior & styling
45
+
46
+ - A single `useEffect` locks body scroll (`document.body.style.overflow = "hidden"`) while
47
+ open and restores it on close.
48
+ - Backdrop click **always** calls `onClose` (not suppressible).
49
+ - CSS Modules (`tableRecordModal.module.css`) with position keyframes (`slideFromRight/Left/
50
+ Top/Bottom`, `fadeScaleIn`, `fadeIn`) and CSS variables for dimensions
51
+ (`--tableRecordModal-right-width` default 480px, etc.).
52
+ - Shares the `TableTheme` className-override type from `Table/themeConfig/types.ts`.
53
+
54
+ ## Relationship to Table
55
+
56
+ No direct import coupling. The consuming app wires it: on `PrimaryTable`'s `onRowClick(row)`
57
+ it opens the modal with `isOpen={!!selectedRow}` and renders the record inside `children`
58
+ (often using the same `tableViewFields` metadata to format fields). `utils/resolveModalType.tsx`
59
+ and `types.ts` are present but **empty/reserved** for a future field-type→renderer mapping.
60
+
61
+ ## Gotchas
62
+
63
+ - **No portal** — renders inline in the React tree; a parent `overflow: hidden` can clip it.
64
+ - **No focus trap / ARIA** (`role="dialog"`, `aria-modal`) — add in consumer.
65
+ - **No modal stacking guard** — nested modals overwrite the saved body-overflow value.
66
+ - CSS-variable defaults are hardcoded fallbacks; set vars on the DOM before mount to change sizes.
67
+
68
+ ## Change history
69
+ - 2026-06-23 — Documented TableRecordModal as a generic modal shell: positions, render-prop children, body-scroll lock, and consumer-owned field rendering / a11y (apeterson).
@@ -0,0 +1,141 @@
1
+ ---
2
+ title: Table component (cells, action cells, filters & sorts, hooks, theming)
3
+ framework: "2.0"
4
+ repo: toga-blox
5
+ project: TOGa Blox
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-23
10
+ owners: [apeterson]
11
+ files:
12
+ - toga-blox/src/components/Table/index.ts
13
+ - toga-blox/src/components/Table/types.ts
14
+ - toga-blox/src/components/Table/utils/buildTanstackColumns.tsx
15
+ - toga-blox/src/components/Table/utils/resolveCellType.tsx
16
+ - toga-blox/src/components/Table/components/cellTypes
17
+ - toga-blox/src/components/Table/components/actionCells
18
+ - toga-blox/src/components/Table/components/columnFiltersAndSorts
19
+ - toga-blox/src/components/Table/hooks
20
+ - toga-blox/src/components/Table/themeConfig
21
+ related:
22
+ - ../architecture.md
23
+ - primary-table-templates.md
24
+ - api-client.md
25
+ ---
26
+
27
+ ## Summary
28
+
29
+ The `Table` component (`src/components/Table/`) is the **TanStack Table v8** building block
30
+ behind the [Primary Table templates](primary-table-templates.md). It is organized in layers:
31
+
32
+ - **Primitives** — thin HTML wrappers (`Table`, `TableHead`, `TableBody`, `TableRow`,
33
+ `TableHeaderCell`, `TableCell`), exported from `src/components/Table/index.ts`.
34
+ - **Column building** — `buildTanstackColumns()` turns a `TableViewField[]` (API metadata)
35
+ into TanStack `ColumnDef[]`; `resolveCellType()` picks the right cell renderer per field type.
36
+ - **Cell types** (`components/cellTypes/`) — the renderers for each data type.
37
+ - **Action cells** (`components/actionCells/`) — row-level action buttons (chevron, download).
38
+ - **Filters & sorts** (`components/columnFiltersAndSorts/`) — per-column header filter/sort UI.
39
+ - **Hooks** (`hooks/`) — data fetching (paginated/infinite), edit state, metadata, filter options.
40
+ - **Theming** (`themeConfig/`) — `ThemeProvider` + skins + CSS variables.
41
+
42
+ ## Column building & cell resolution
43
+
44
+ `utils/buildTanstackColumns.tsx` — `buildTanstackColumns({ tableViewFields, isEditable,
45
+ updateCell, typeValues?, skin?, onNavigate?, userSettings?, columnWidths?, columnTypeConfig? })`
46
+ returns `ColumnDef<any>[]`. Per visible field it sets `id = slug`, `header = label`,
47
+ `enableSorting/Filtering`, a custom `filterFn` (`columnFilterFn` supports modes
48
+ **startsWith, endsWith, exactly, includes, excludes**), an `accessorFn` that walks
49
+ dot-notation paths (`related.field`), and a `cell` that delegates to `resolveCellType()`.
50
+
51
+ **Column width precedence** (`buildTanstackColumns.tsx:112-123`): explicit `columnWidths`
52
+ (set as both `size` and `minSize`) → `columnTypeConfig`/`COLUMN_TYPE_CONFIG` → field `width`
53
+ → default 200px size / `MIN_COLUMN_WIDTH` (=120, line 9) minSize. `COLUMN_TYPE_CONFIG`
54
+ defaults: DATETIME_CREATED 160, STATUS 140, URGENCY 100, CLIENT 180.
55
+
56
+ `utils/resolveCellType.tsx` maps `tableViewField.type` → a cell component and wraps it for
57
+ alignment (CVA `cellWrapperVariants`), expandable, and copyable behavior. Empty/null values
58
+ render `EmptyCell`. If `imageUrlField` is set, the URL is resolved separately and passed to
59
+ `ImageCell`.
60
+
61
+ ## Cell types (`components/cellTypes/`)
62
+
63
+ | Component | Type | Renders |
64
+ |---|---|---|
65
+ | `TableCell` / `TableHeaderCell` | generic | base `<td>` / `<th>` wrappers (forwardRef) |
66
+ | `DateCell` | DATE/DATETIME | dayjs-formatted time (top) + date (bottom); optional `timeZone` (dayjs.tz); default `MM/DD/YYYY` + `hh:mm A` |
67
+ | `CurrencyCell` | CURRENCY | `$` + `toLocaleString("en-US")` to `precision` |
68
+ | `NumberCell` | NUMBER | localized number, `maximumFractionDigits = precision` |
69
+ | `StatusCell` | STATUS | colored dot + label from `typeValues` (fallback `#CBD5E1`) |
70
+ | `StatusBadgeCell` | STATUS_BADGE | filled badge, background from `typeValues` |
71
+ | `UrgencyCell` | URGENCY | FontAwesome flag icon + label, colored |
72
+ | `BooleanCell` | BOOLEAN | "Yes"/"No" with green (`#2CB224`) / red (`#FF2E54`) dot |
73
+ | `EmptyCell` | — | em-dash placeholder |
74
+ | `ImageCell` | IMAGE | image (or gray placeholder) + label |
75
+ | `CopyableCell` | any | wraps content with copy-to-clipboard button (`buttonPosition` left/right) |
76
+ | `ClientCell` / `UserAvatarCell` | CLIENT/AVATAR | hash-colored initials avatar + name |
77
+ | `ExpandableCell` | text | truncates at `maxLength` (default 80) with show more/less |
78
+ | `EditableCell` | edit mode | type-specific inline input (text/number/currency/date/boolean); commits on blur |
79
+
80
+ ## Action cells (`components/actionCells/`)
81
+
82
+ `ChevronActionCell` (row expand/collapse; rotates on `isActive`) and `DownloadActionCell`
83
+ (download button, `aria-label`, `stopPropagation`). Both take `{ row, onAction, isActive,
84
+ isHovered, skin, isDisabled }`. They are inserted as columns via `buildActionColumn()` +
85
+ `insertActionColumns()` (sorted by `position`: "start" → -∞, "end" → +∞, number → index).
86
+
87
+ ## Column filters & sorts (`components/columnFiltersAndSorts/`)
88
+
89
+ Per-column header controls, each a popover trigger with `data-active` / `data-open`:
90
+
91
+ - **`HeaderFilterSearch`** — text filter, 5 modes (startsWith/endsWith/exactly/includes/
92
+ excludes) chosen from a **mode dropdown that excludes the currently selected mode**.
93
+ **Tag-based**: confirmed values become removable tags (`activeTags`); `isActive =
94
+ activeTags.length > 0`. The mode dropdown is portalled to `document.body` with fixed
95
+ positioning recomputed on scroll. The search icon (solid when active, regular otherwise)
96
+ is hidden while the input is focused; both the trigger button and the icon span carry
97
+ `data-active`. The clear button must not widen the input row (fixed-width menu).
98
+ - **`HeaderFilterRange`** — numeric, modes exactly/moreThan/lessThan + a range toggle
99
+ (min/max); emits `{ key, value }[]` (`eq`/`min`/`max`/`between`).
100
+ - **`HeaderFilterMultiselect`** — checkbox list with "Select All"; emits comma-joined slugs.
101
+ - **`HeaderFilterDate`** — calendar picker, modes exactly/before/after + range; converts
102
+ `MM/DD/YYYY` ↔ URL `YYYY-MM-DD`; calendar + mode dropdown both portalled.
103
+ - **`HeaderFilterButton`** — generic trigger: shows a numbered badge when active with a
104
+ `filterIndex`, otherwise the search icon (`data-active`).
105
+ - **`SortIcon`** — asc/desc/reset menu with a multi-sort index badge; button visibility keys
106
+ off current sort direction.
107
+
108
+ ## Hooks (`hooks/`)
109
+
110
+ | Hook | Returns / does |
111
+ |---|---|
112
+ | `useTableSetup` | builds columns via `buildTanstackColumns` + `insertActionColumns`; memoized `updateCell`; tracks hovered row uuid |
113
+ | `useTableData` | runs **either** paginated or infinite query by `isPaginationEnabled` (single source of truth); shared `["table-data", slug, …]` cache prefix; strips display-only URL params (`slug`, `cols`) so they don't refetch |
114
+ | `useTablePageData` | paginated-only variant |
115
+ | `useInfiniteTableData` | `useInfiniteQuery` + IntersectionObserver on a `sentinelRef` |
116
+ | `useTableInfiniteScroll` | virtualizer-based "near end" trigger (lower-level alternative) |
117
+ | `useTableEdit` | dirty-row tracking, dot-notation patch, `handleSave` calls `mutationFn` and updates the query cache, clears dirty + exits edit mode on success |
118
+ | `useFetchTablePageMeta` | fetches table meta + fans out filter-option queries per field |
119
+ | `useAssignTableFieldLabels` | merges page-level labels into field defs |
120
+ | `useTableFieldFilterOptions` | filter options for a single field |
121
+
122
+ ## Theming (`themeConfig/`)
123
+
124
+ `ThemeProvider` injects a `ClientTheme { id, name, vars }` as CSS variables on
125
+ `document.documentElement`. `TableTheme` (types.ts) is a className-override map
126
+ (`wrapper`, `headerCell`, `bodyRow`, `stickyLeft/Right`, `filterPopover`, …). Skins:
127
+ `supply` (default), `desk`, `supply-nested` (compact, max-height 480px), `supply-modal-table`
128
+ (modal: fixed heights, virtualization disabled). Design tokens are `--primaryTable-*` in
129
+ `toga.module.css`.
130
+
131
+ ## Gotchas
132
+
133
+ - `MIN_COLUMN_WIDTH = 120` is the shrink floor only for columns with **no** explicit width.
134
+ - The filter `includes` mode treats comma-separated values as OR.
135
+ - `EditableCell` currency formats **on blur**, phone formats **on keystroke**.
136
+ - Cell type config (`columnTypeConfig` prop) overrides the hardcoded `COLUMN_TYPE_CONFIG`.
137
+
138
+ ## Change history
139
+ - 2026-06-23 — Documented the Table component layers, cell types, filters/sorts, hooks, and theming (apeterson).
140
+ - 2026-06-23 — `HeaderFilterSearch`: search icon hidden on input focus; `data-active` added to the icon span; clear button no longer widens the input (fixed-width menu); mode dropdown excludes the selected mode (apeterson).
141
+ - 2026-06-22 — Column sizing reworked to measure-and-freeze using natural content width; `MIN_COLUMN_WIDTH` floor (120px) applies only when no explicit width is set (apeterson).
@@ -28,6 +28,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
28
28
  - **ai-bdr** (AI-BDR) — 4 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
29
29
  - **toga2-commerce** (TOGa Commerce) — 2 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
30
30
  - **toga25-supply** (TOGa 2.5 Supply) — 5 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
31
+ - **toga-blox** (TOGa Blox) — 7 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
31
32
 
32
33
  ## standalone framework
33
34
 
@@ -175,5 +175,12 @@
175
175
  "framework": "2.0",
176
176
  "role": "app",
177
177
  "dependsOn": []
178
+ },
179
+ {
180
+ "repo": "toga-blox",
181
+ "project": "TOGa Blox",
182
+ "framework": "2.0",
183
+ "role": "app",
184
+ "dependsOn": []
178
185
  }
179
186
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.165",
3
+ "version": "1.0.166",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",