|
| 1 | +# Plan 004: agility-rooms adopts `@versionone/agility-sdk` (Track D) |
| 2 | + |
| 3 | +Brief for a session on `digital-ai/agility-rooms`, a React/Vite SPA that |
| 4 | +runs inside Agility (same-origin, cookie auth). Source plan: |
| 5 | +`plans/001-sdk-adoption-campaign.md`, Track D. gh-v1 (plan 002) and the |
| 6 | +slack bot (plan 003) have finished their adoptions; this is the last |
| 7 | +consumer, and the slowest: a strangler that starts below the call sites so |
| 8 | +nothing user-visible moves. |
| 9 | + |
| 10 | +## Goal |
| 11 | + |
| 12 | +Rooms talks to the Agility data endpoints (api/asset, query.v1, rest-1, |
| 13 | +meta.v1, loc-2, attachments, embedded images) only through |
| 14 | +`@versionone/agility-sdk`. Rows arrive translated, so its hand-decoding |
| 15 | +goes. UI behaviour stays the same. Rooms' own routes (`api/*` reports, |
| 16 | +tags, watch, rooms2config, LLM; `*.mvc` pages) are out of scope unless the |
| 17 | +SDK already covers them. |
| 18 | + |
| 19 | +## Where things stand (surveyed 2026-11-07 at `main` `0874590`, read-only) |
| 20 | + |
| 21 | +Full survey: plan 001, *What the consumers do today*, the agility-rooms |
| 22 | +re-survey paragraph. In short: |
| 23 | + |
| 24 | +- **HTTP.** `src/utils/axios.tsx` builds the default instance (`baseURL` |
| 25 | + the app root, `X-Requested-With`, cookie auth; on 401/403 an SSO popup |
| 26 | + via `Account.mvc/Login`, then axios-auth-refresh replays). A response |
| 27 | + interceptor turns api/asset `commandFailures` into a localized snackbar |
| 28 | + and throws — but only when the URL is exactly `/api/asset`; 9 calls |
| 29 | + written `api/asset` bypass it. `getAxiosInstance(...)` variants, a bare |
| 30 | + `meta` instance and a plain `axios.create` in `src/navigation/routes.tsx` |
| 31 | + skip some interceptors. 88 production files import axios. |
| 32 | +- **Endpoints.** api/asset (111 call sites in 57 files), query.v1 (16), |
| 33 | + rest-1 reads (4) **and writes** (5 `Data/{T}/{id}` attribute posts, 2 |
| 34 | + `?op=Copy`, plus `New/{T}?ctx=` templates), meta.v1, loc-2, `loc.v1`, |
| 35 | + `config.v1`, attachments (`MultiValueRelations/Attachments.tsx`: inline |
| 36 | + Base64 create, `attachment.img` download), embedded images |
| 37 | + (`useImageUpload.ts` → `EmbeddedImage.mvc/Upload`). |
| 38 | +- **Reads.** Positional `results[n]` with index constants |
| 39 | + (`metaAssetStore/constants.ts`); some board and `ID=$Oids` reads are |
| 40 | + unbounded (the SDK refuses an unpaged query). |
| 41 | +- **Decoding.** 46 `"True"`/`"False"` comparisons; `NULL_OID` used 118 |
| 42 | + times; `massageApiAssetResponseObject` unwraps single relations. meta.v1 |
| 43 | + is loaded by `store/metaDefinition/useMetaDefinitionStore.ts`. |
| 44 | +- **Tests.** Jest (222 files, module mocks, no MSW); the AI domain's |
| 45 | + injected `IHttpClient` / `FakeHttpClient` is the cleanest seam. |
| 46 | + Playwright (79 specs) runs against a live server. |
| 47 | + |
| 48 | +## What the SDK offers rooms (1.0.0-alpha.0) |
| 49 | + |
| 50 | +- `AgilityApiClient` with a pluggable `Transport` (D1 wraps rooms' axios), |
| 51 | + or `createFetchTransport` with `credentials: "same-origin"` and |
| 52 | + `onUnauthorized` (resend an idempotent request after re-auth; a write is |
| 53 | + never resent), `signal` / `client.withSignal` for cancellation. |
| 54 | +- `client.assets.query/queries/send` (rows translated through the schema; |
| 55 | + a failed query throws; `CommandFailureError` carries each failure by |
| 56 | + index for the snackbar), `client.queryV1`, `client.data` (rest-1 reads), |
| 57 | + `client.localize`, `client.schema` / `TypeCache`, `client.resolve`, |
| 58 | + `client.batch` (guarded sends, receipts, undo). |
| 59 | +- `client.attachments` (list/page/info/get/add/put/remove/url; inline |
| 60 | + Base64 up to 1 MB as rooms does, two-step above) and |
| 61 | + `client.embeddedImages` (upload/url/normalize). |
| 62 | +- Browser-safe: no `node:` imports, no Buffer. Install |
| 63 | + `npm install @versionone/agility-sdk@1.0.0-alpha.0` (an exact pin; it is |
| 64 | + an alpha). |
| 65 | + |
| 66 | +## First: a decision for Jim |
| 67 | + |
| 68 | +Rooms writes through rest-1 (attribute posts with `act`, `?op=Copy`) and |
| 69 | +reads `New/{T}` templates, which plan 001 assumed no consumer needed. |
| 70 | +Before D1, find out (live, on v1sdktesting) whether api/asset covers each: |
| 71 | +an update for the attribute posts (likely), an `execute: "Copy"` or similar |
| 72 | +for Copy, and whether `New/{T}` has an api/asset equivalent. Report what you |
| 73 | +find to the SDK session and Jim, with a recommendation: move them to |
| 74 | +api/asset, or have the SDK grow a narrow rest-1 write path. |
| 75 | + |
| 76 | +## Steps, one PR each against `main`, from its own branch |
| 77 | + |
| 78 | +1. **R1 Narrow the axios surface.** Route the axios importers through |
| 79 | + `utils/bulkApiService.tsx` and a small set of helpers, so D1 swaps one |
| 80 | + module. Fix the `api/asset` vs `/api/asset` interceptor gap on the way. |
| 81 | +2. **D1 SDK under the existing axios.** A `Transport` that delegates to |
| 82 | + rooms' axios instance (keeps the SSO refresh and the snackbar); point |
| 83 | + `bulkApiService.tsx` at `client.assets.*`. Rows arrive translated, so |
| 84 | + `getApiResults`' `IsReadOnly` patch goes. |
| 85 | +3. **D2 Domain by domain.** Start with `src/domains/ai`, then retire the |
| 86 | + string-boolean and `NULL_OID` decodes per feature; attachments onto |
| 87 | + `client.attachments`, embedded images onto `client.embeddedImages`; |
| 88 | + positional `results[n]` onto named or `queries` results; page every read. |
| 89 | +4. **D3 Native transport.** Replace the axios-backed Transport with |
| 90 | + `createFetchTransport({ credentials: "same-origin", onUnauthorized })`, |
| 91 | + the SSO popup as the hook. |
| 92 | +5. **D4 Schema convergence.** Evaluate replacing parts of |
| 93 | + `src/metaAssetStore/` and `useMetaDefinitionStore` with the SDK's |
| 94 | + `schema` / `TypeCache`. |
| 95 | + |
| 96 | +## Rules |
| 97 | + |
| 98 | +- Jest and the existing Playwright gates pass on every PR. No behaviour or |
| 99 | + UI change unless a step says so. |
| 100 | +- Live Agility calls go only to `https://www14.v1host.com/v1sdktesting` |
| 101 | + (`V1_URL`, token in `V1_ACCESS_TOKEN`; Node's fetch needs |
| 102 | + `NODE_USE_ENV_PROXY=1`). Writes are allowed there and nowhere else: name |
| 103 | + assets `sdk-integration …` and delete them by exact OID. Every query is |
| 104 | + small and explicitly paged. Point Playwright at nothing else. |
| 105 | +- An EmbeddedImage cannot be deleted; upload one only when a test needs it. |
| 106 | +- If the SDK lacks something, don't work around it in rooms: tell the SDK |
| 107 | + session (`session_016NSWj9gL1oCubj5e2anVkb`, `send_message`) with what |
| 108 | + you need and why. Don't request reviewers; Jim picks. |
| 109 | + |
| 110 | +## Done when |
| 111 | + |
| 112 | +- Rooms reaches the data endpoints only through the SDK; no file outside |
| 113 | + the transport adapter imports axios for them. |
| 114 | +- No `"True"`/`"False"` comparisons or `NULL_OID` checks on SDK rows remain. |
| 115 | +- Attachments and embedded images go through the SDK. |
| 116 | +- Plan 001's checklist line `D1–D4 agility-rooms` is ticked, with PR links |
| 117 | + (tell the SDK session as each merges). |
0 commit comments