toga-ai 1.0.1069 → 1.0.1070

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.
@@ -17,6 +17,7 @@
17
17
  | [Record Scripts (computed/aggregate /v2 endpoints — the authoring contract)](features/record-scripts.md) | The authoring contract for a Record Script — a `public static` model method that becomes a read-only computed/aggregate `/v2` endpoint (double-nested envelope, |
18
18
  | [V2 Request Logging & Where Requests Land (client vs core log DB)](features/request-logging.md) | Where each V2 request lands (client vs core/writer Logs), when it leaves no row at all (PRE-interceptor throw, uncaught fatal), the apiproxy-retry / duplicate-t |
19
19
  | [POST + JSON-body args for scripted APIs](features/scripted-api-post-body-args.md) | How the V2 engine runs a Record Script for a POST and lets scripted args arrive in the JSON body (to avoid HTTP 414 on large values); open when a scripted API n |
20
+ | [Session currency (audience.currency + JWT id.currency)](features/session-currency.md) | How api2 resolves the user's currency once per session and returns it as `audience.currency` (ISO code or null) and JWT `id.currency`; open when a front end nee |
20
21
  | [TOGa Talos Sprint Dashboard API (Record Scripts)](features/sprint-dashboard-api.md) | The six api2 Record Scripts on `_Model_Team_Sprint` that serve the internal TOGa Talos sprint dashboard from the `Team` schema; open when consuming/registering |
21
22
  | [Surface action-state via the surface=<slug> request option (M2M-safe)](features/surface-meta-option.md) | The opt-in `surface=<slug>` GET option that attaches per-record UI action state under `meta.surface` (never `data`); open when wiring the Surface layer to a scr |
22
23
  | [TableView row-filtering via apiWhereClause (options.where grammar, end to end)](features/tableview-apiwhereclause-row-filtering.md) | `TableViews.apiWhereClause` (TEXT, nullable) is the sanctioned, code-free way to restrict or exclude rows from a 2.0 table view. |
@@ -6,7 +6,7 @@ project: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-09
9
+ updated: 2026-10-17
10
10
  owners: ["jcardinal", "bala"]
11
11
  files:
12
12
  - api2/Component/Api/V2/V2.php
@@ -102,7 +102,7 @@ Placement is load-bearing: the `id.language` mutation happens **after** client-D
102
102
  - **Per-row cost on translatable-join lists (site 5).** `getTranslatedFieldValue` dereferences the source model key via `_Model __get`, forcing a full row load, so ~2 queries per row (source + sidecar) for non-English translatable-join lists — consistent with the feature's per-row-load tradeoff. Candidate future optimization: let the helper accept a known id instead of a live `_Model`.
103
103
  - **Pre-existing (not introduced by site 5): `join=`/`ojoin=` identifiers are unvalidated.** Table/alias/ON-clause identifiers flow from the query string into SQL with no validation/escaping (`V2.php` ~L3416-3463, `parseOptionsJoin` ~L7874-7920). The new PK-select line reuses the same already-tainted `$tableAlias` as the adjacent pre-existing lines, so it adds no new injection class — but the underlying tainted-identifier path is a standing concern flagged for separate follow-up.
104
104
  - Reading a possibly-absent magic field off a generic `_Model` needs `array_key_exists(...)` + `__get` — `isset()`/`?? null` always read false/null. See [_Model magic-field access](../../_underscore/features/model-magic-field-access.md); this was the cause of the uuid not appearing in the fallback warning.
105
- - **Refresh re-resolution is user-token-only.** If you extend the JWT with new audience-shaped claims, remember the refresh path rebuilds context from the *existing* token (uuid/app-uuid/personas) — it does not re-read anything the token doesn't carry.
105
+ - **Refresh re-resolution is user-token-only.** `audience.currency` reuses this cascade + refresh path — see [Session currency](./session-currency.md). If you extend the JWT with new audience-shaped claims, remember the refresh path rebuilds context from the *existing* token (uuid/app-uuid/personas) — it does not re-read anything the token doesn't carry.
106
106
  - Migrations not yet executed at time of writing; needs live verification.
107
107
 
108
108
  ## Related
@@ -0,0 +1,62 @@
1
+ ---
2
+ title: Session currency (audience.currency + JWT id.currency)
3
+ framework: "2.0"
4
+ repo: api2
5
+ project: API
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-10-17
10
+ owners: ["jcardinal"]
11
+ files:
12
+ - api2/Component/Api/V2/V2.php
13
+ - api2/Component/Api/V2/Response/Response.php
14
+ - _underscore/Model/Client/Currency.php
15
+ - _underscore/Model/Client/UserGlobalSetting.php
16
+ - _underscore/Model/Client/ItemPrice.php
17
+ - dbchanges2/Core/2026-10-02a - PriceDefaultCurrency.sql
18
+ - dbchanges2/Client/2026-10-02a - AllClients PriceCurrency.sql
19
+ related:
20
+ - ./language-translation-layer.md
21
+ - ../../toga25-blox/features/money-formatting.md
22
+ - ../../../../clients/quad/features/multi-currency-item-pricing.md
23
+ ---
24
+ How api2 resolves the user's currency once per session and returns it as `audience.currency` (ISO code or null) and JWT `id.currency`; open when a front end needs the session currency, a currency setting is ignored, or WZ-5 appears.
25
+
26
+ ## Summary
27
+
28
+ Every authenticated api2 response carries `audience.currency` — a 3-letter ISO code (`USD`) or `null`. The same value is in the JWT `id.currency` claim. It is resolved at sign-in and on `/auth/refresh`, exactly like `audience.language` (one value per session, no per-request cost). Front ends format money with it through blox — see [money formatting](../../toga25-blox/features/money-formatting.md). Uniform across clients.
29
+
30
+ ## How it works
31
+
32
+ **Cascade.** Settings slug `currency` (`Core.Settings` id 14, uuid `daa25a05-c1c7-4dcc-6a7c-c79a3c02b92a`; `Core.DefaultGlobalSettings` value `USD`, seeded by `dbchanges2/Core/2026-10-02a - PriceDefaultCurrency.sql`, present in prod Core — verified 2026-10-17). Walked by the same `resolveSettingValue()` 8-layer cascade as language (DefaultGlobal → … → UserApp, `isOverridable = 0` locks, first persona wins) — see [Language Translation Layer → Language resolution](./language-translation-layer.md#how-it-works).
33
+
34
+ **Two stored shapes — both valid, storage left as-is.**
35
+ - Core default = ISO code (`USD`).
36
+ - Client / persona / user rows = a Client `Currencies.uuid`. Commerce and Quad read that uuid (`_underscore` `UserGlobalSetting` / `ItemPrice` treat the value uuid-first, then code) — do not convert stored rows to codes.
37
+
38
+ **Validation (V2.php).**
39
+ 1. Value looks like a uuid → load `_Model_Client_Currency` by uuid on the user's **own client connection** and return `Currencies.code`. Never a Core↔Client join (separate clusters).
40
+ 2. Otherwise accept only `^[A-Z]{3}$`.
41
+ 3. `''` = not set (next layer wins).
42
+ 4. Unusable value → re-resolve with client-level layers only (default + client, no persona/user); still nothing → `null` + warning **WZ-5** (`Response.php`, `{currency}` placeholder, value cut to 64 chars). Never fails the sign-in or refresh.
43
+
44
+ **Token paths.**
45
+ - Sign-in: resolved, put in `id.currency`, echoed as `audience.currency`.
46
+ - `/auth/refresh` (user tokens only): re-resolved like language, so a change shows on the next refresh.
47
+ - View-as: keeps the delegator's validated currency. A delegator token from before this change (no `currency` key) resolves normally.
48
+ - API-key auth: `null`.
49
+
50
+ ## Gotchas
51
+
52
+ - `Client_Quad` has a `ClientGlobalSettings` currency row with value `''` — counts as not set, falls through to the Core `USD` default.
53
+ - Local `Client_True` / `Client_Elite` `Currencies` tables are empty, so a uuid-valued setting resolves to null + WZ-5 locally. Prod has USD via `dbchanges2/Client/2026-10-02a - AllClients PriceCurrency.sql`. That file has one-time schema steps — do not re-run the whole file locally; insert the row by hand.
54
+ - Changing a user's currency (UI, `isOverridable` fan-out, own-row ACL write guard) is not built yet — later ticket. Mirror the language write guard in [API Payload Interceptors](./api-payload-interceptors.md#own-row-guards-in-a-pre-interceptor) when it is.
55
+
56
+ ## Related
57
+
58
+ - [Language Translation Layer](./language-translation-layer.md) — the shared cascade + refresh mechanics.
59
+ - [Money formatting (toga25-blox)](../../toga25-blox/features/money-formatting.md) — the front-end consumer.
60
+
61
+ ## Change history
62
+ - 2026-10-17 — Added `audience.currency` / `id.currency`, resolved at sign-in + refresh; uuid → code on the client connection; WZ-5 fallback. Proven by a real local sign-in + refresh. TRUE-82878 (jcardinal)
@@ -4,6 +4,7 @@
4
4
  |-----|---------|
5
5
  | [TOGa Blox 2.5 — Architecture](architecture.md) | Shared React+TS SOURCE library for the new platform apps (new platform apps first; replaces toga-blox for all apps over time); read the ADR for full detail, not |
6
6
  | [Contact primary email/phone save (updateContactChannels) — table calls only](features/contact-channels-save.md) | How a contact edit form saves the contact's primary email and phone without nested writes; use it (do not rebuild it) in any app that edits a contact's email/ph |
7
+ | [Money formatting (useSessionCurrency / formatMoney / useFormatMoney)](features/money-formatting.md) | The one money formatter for every platform app (session currency + session language via Intl); open before showing any money amount or adding a money column. |
7
8
  | [Old blox (toga-blox) straight port inside toga25-blox](features/old-blox-port.md) | Old `@agilant/toga-blox` code copied unchanged into toga25-blox (blox-v1 layer); open before touching any old component, its checks/config, or merging Storybook |
8
9
  | [GitHub Actions → AWS role via OIDC (trust policy + sub claim)](workflows/github-actions-aws-oidc-role.md) | How a GitHub Actions job assumes an AWS IAM role with OIDC, and the `sub` claim trap; open before creating or debugging any GitHub-to-AWS role ("Not authorized |
9
10
  | [Moving an old app from @agilant/toga-blox onto toga25-blox](workflows/moving-an-old-app-onto-toga25-blox.md) | Planned steps to switch an old app off the `@agilant/toga-blox` npm package onto toga25-blox source with zero import edits; open before moving any old app. |
@@ -0,0 +1,45 @@
1
+ ---
2
+ title: Money formatting (useSessionCurrency / formatMoney / useFormatMoney)
3
+ framework: "2.0"
4
+ repo: toga25-blox
5
+ project: TOGa Blox 2.5
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-10-17
10
+ owners: ["jcardinal"]
11
+ files:
12
+ - toga25-blox/src/core/format/formatMoney.ts
13
+ - toga25-blox/src/core/auth/useSessionCurrency.ts
14
+ - toga25-blox/src/core/http/currencyStore.ts
15
+ - toga25-blox/src/data-table/record-table/cellRegistry.tsx
16
+ - toga25-blox/src/data-table/record-table/ColumnHeaderControls.tsx
17
+ related:
18
+ - ../../api2/features/session-currency.md
19
+ - ../architecture.md
20
+ ---
21
+ The one money formatter for every platform app (session currency + session language via Intl); open before showing any money amount or adding a money column.
22
+
23
+ ## Summary
24
+
25
+ `formatMoney(value, {currency, locale})` and the hook `useFormatMoney()` are THE money formatter for every platform app. The currency comes from `useSessionCurrency()` (api2 `audience.currency` — see [session currency](../../api2/features/session-currency.md)); the locale is the session language. Never hard-code a currency or a `$` in front-end code.
26
+
27
+ ## How it works
28
+
29
+ - `currencyStore.ts` keeps `audience.currency` from each api2 response (wired in `apiClient.ts`, `credentials.ts`, `auth/session.ts`). `useSessionCurrency()` returns the ISO code or `null`.
30
+ - `formatMoney` uses `Intl.NumberFormat` with the session language as locale and the ISO code as currency. It does not use `Currencies.symbol` / `position` — Intl places the symbol per language.
31
+ - `null` or invalid currency → the number with no symbol (never a guessed `$`).
32
+ - Record-table default cells format money through `cellContext.formatMoney` — a money column needs no custom cell.
33
+ - **Right-aligned columns:** set `align: "right"` on a blox column (header + cells; the label stays first in tab order). Do not write app CSS that reaches into blox table internals to align a column.
34
+
35
+ **Which currency for an amount (decided 2026-10-17, jcardinal):**
36
+ - A stored amount with no currency column of its own is in its **owning client's** currency.
37
+ - Multi-client listings use the session currency for now — all clients are USD.
38
+ - Formatting by owning client must be built **before any client goes non-USD** (tracked follow-up).
39
+
40
+ ## Gotchas
41
+
42
+ - Quad stores many currencies per item — its listings render per-currency text, not one formatted number. See [Quad multi-currency item pricing](../../../../clients/quad/features/multi-currency-item-pricing.md).
43
+
44
+ ## Change history
45
+ - 2026-10-17 — Added session currency + shared money formatter; record-table money cells and `align: "right"` columns. TRUE-82878 (jcardinal)
@@ -5,7 +5,7 @@ project: _Underscore
5
5
  client: shared
6
6
  type: standard
7
7
  status: active
8
- updated: 2026-10-05
8
+ updated: 2026-10-17
9
9
  owners: [jcardinal, apeterson, tcox]
10
10
  files: []
11
11
  related:
@@ -755,6 +755,17 @@ Copy from there rather than reinventing.
755
755
 
756
756
  ---
757
757
 
758
+ ## 24. Money display — **MUST**
759
+
760
+ - Format every money amount with toga25-blox `useFormatMoney()` / `formatMoney()` (session
761
+ currency from api2 `audience.currency`, session language as locale). Never hard-code a
762
+ currency code or `$`, and never build the symbol from `Currencies.symbol`/`position`.
763
+ - Money columns: use the record-table default money cell and blox column `align: "right"` —
764
+ no app CSS reaching into blox table internals.
765
+ - Detail + the "owning client's currency" decision: [Money formatting](../apps/toga25-blox/features/money-formatting.md).
766
+
767
+ ---
768
+
758
769
  # Part 3 — Open items
759
770
 
760
771
  *(Described and flagged. NOT prescribed as settled.)*
@@ -23,7 +23,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
23
23
 
24
24
  - **_underscore** (_Underscore) _(framework core)_ — 104 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
25
25
  - **worker2** (Worker) — 90 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
26
- - **api2** (API) — 26 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
26
+ - **api2** (API) — 27 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
27
27
  - **dbchanges2** (Database Changes) _(framework core)_ — 22 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
28
28
  - **toga2-supply** (TOGa Supply) — 9 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
29
29
  - **saml** (SAML SSO Gateway) — 9 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
@@ -34,7 +34,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
34
34
  - **toga25-supply** (TOGa 2.5 Supply) — 26 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
35
35
  - **toga25-iq** (TOGa IQ) — 1 doc(s) → [2.0/apps/toga25-iq/INDEX.md](2.0/apps/toga25-iq/INDEX.md)
36
36
  - **toga25-desk** (TOGa Desk 2.5) — 1 doc(s) → [2.0/apps/toga25-desk/INDEX.md](2.0/apps/toga25-desk/INDEX.md)
37
- - **toga25-blox** (TOGa Blox 2.5) — 5 doc(s) → [2.0/apps/toga25-blox/INDEX.md](2.0/apps/toga25-blox/INDEX.md)
37
+ - **toga25-blox** (TOGa Blox 2.5) — 6 doc(s) → [2.0/apps/toga25-blox/INDEX.md](2.0/apps/toga25-blox/INDEX.md)
38
38
  - **toga-blox** (TOGa Blox) — 15 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
39
39
 
40
40
  ## standalone framework
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1069",
3
+ "version": "1.0.1070",
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",