@foldspace_npm/harness 0.1.7 → 0.1.9
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/README.md +38 -8
- package/bin/build.ts +20 -0
- package/bin/lint.mjs +9 -0
- package/package.json +10 -2
- package/src/cli-registry.mjs +25 -1
- package/src/init.mjs +3 -1
- package/src/lint/ast.mjs +237 -0
- package/src/lint/index.mjs +101 -0
- package/src/lint/rules.mjs +464 -0
- package/src/lint/ts.mjs +6 -0
- package/src/runtime/agent.ts +66 -0
- package/src/runtime/config.ts +78 -0
- package/src/runtime/http.ts +373 -0
- package/src/runtime/index.ts +36 -0
- package/src/runtime/match.ts +87 -0
- package/src/runtime/render.ts +97 -0
- package/templates/agent-starter/CLAUDE.md +82 -21
- package/templates/agent-starter/agent/actions/_example.ts +20 -25
- package/templates/agent-starter/agent/actions/index.ts +9 -1
- package/templates/agent-starter/agent/constants.ts +25 -0
- package/templates/agent-starter/agent/utils.ts +59 -12
- package/templates/agent-starter/docs/app-profile.md +109 -0
- package/templates/agent-starter/package.json +2 -0
- package/templates/agent-starter/tsconfig.json +1 -0
|
@@ -29,11 +29,15 @@ from actions that read or mutate a selected resource.
|
|
|
29
29
|
3. Capture at least one real HTTP 200 for the data the action needs. Use
|
|
30
30
|
chrome-devtools MCP against the inject Chrome, or a page-context `fetch`,
|
|
31
31
|
**before** `attach` owns the debug port. If there is no 200,
|
|
32
|
-
pivot; do not create Foldspace resources.
|
|
32
|
+
pivot; do not create Foldspace resources. Write what you establish into
|
|
33
|
+
`docs/app-profile.md` with **how you know it**. Do not promote an assumption
|
|
34
|
+
by quoting that file.
|
|
33
35
|
4. Create action metadata as a draft. `generate_action_handler` works from the
|
|
34
36
|
draft schema; do not publish yet.
|
|
35
37
|
5. Implement the handler using the observed request and response shapes.
|
|
36
|
-
|
|
38
|
+
Import helpers from `agent/utils.ts` — inspect that file before writing
|
|
39
|
+
another `fetch` or rank helper.
|
|
40
|
+
6. Register the handler in `agent/actions/index.ts` and build (`foldspace build` lints first).
|
|
37
41
|
7. Run `npx foldspace attach --daemon` (add `--bootstrap` or `--replace` when
|
|
38
42
|
the page requires it). An empty local registry is valid if you only want to
|
|
39
43
|
see how the agent works.
|
|
@@ -45,17 +49,30 @@ from actions that read or mutate a selected resource.
|
|
|
45
49
|
|
|
46
50
|
Actions execute in the user's signed-in browser session.
|
|
47
51
|
|
|
48
|
-
-
|
|
49
|
-
`
|
|
52
|
+
- Import HTTP, ranking, and widget helpers from `../utils`, not a new
|
|
53
|
+
`agent/api.ts` copy of `fetch`.
|
|
54
|
+
- Do not guess `API_BASE` or `AUTH_SOURCE`. Capture at least one real 200
|
|
55
|
+
(and the auth header the page actually sends) before filling them in.
|
|
56
|
+
`credentials: "include"` is correct only when you have observed the app
|
|
57
|
+
using cookies that way. Many apps use `Authorization: Bearer` from
|
|
58
|
+
`localStorage` instead; some use a custom header.
|
|
50
59
|
- Never guess endpoints or schemas. Capture at least one real 200 before
|
|
51
60
|
implementing a parser. Do not implement a path that was not observed.
|
|
52
61
|
- Verify that the user is signed in before observing a workflow.
|
|
53
62
|
- Do not substitute a public developer API when the browser session is missing;
|
|
54
63
|
ask the user to sign in.
|
|
55
|
-
- Validate parameters and return sanitized errors.
|
|
64
|
+
- Validate parameters and return sanitized errors. Return **data only** — never
|
|
65
|
+
`directive`, `instructions`, or a paragraph telling the copilot what to say.
|
|
66
|
+
Action and agent instructions live in Agent Studio / MCP. Never return
|
|
67
|
+
`ApiResult.detail` from `execute` — it is for the console.
|
|
56
68
|
- Actions that return data the user will inspect should include a `render`
|
|
57
69
|
function for in-chat UI (a chatterblock). If it makes more sense to output the data
|
|
58
70
|
in a UI component instead of text then consider using render to show a component.
|
|
71
|
+
- `render` receives **`execute`'s return value**, not the action's input params.
|
|
72
|
+
Returning anything that lacks the ids the card needs is why widgets pass in
|
|
73
|
+
isolation and fail in the real chat.
|
|
74
|
+
- `runAction` refuses render actions (`cannot be executed silently`). Driving
|
|
75
|
+
`render()` yourself never runs `execute()`, so it does not test that contract.
|
|
59
76
|
- Check https://docs.foldspace.ai/guides/in-chat-ui/ for more information
|
|
60
77
|
|
|
61
78
|
## Task agents
|
|
@@ -70,6 +87,7 @@ known response shape. Those stay in `execute`.
|
|
|
70
87
|
Task agents are created in Agent Studio, not in this repo. Ask before
|
|
71
88
|
creating or publishing one. Call a published task agent from the
|
|
72
89
|
handler with `runTask({ taskKey, data })` (if not published the runTask won't work).
|
|
90
|
+
`data` carries extracted facts only — not `prompt` / `instructions` strings.
|
|
73
91
|
Prefer JSON output when the handler must consume the result.
|
|
74
92
|
|
|
75
93
|
See https://docs.foldspace.ai/user-guides/task-agents/ and
|
|
@@ -114,42 +132,85 @@ execute/render lines; do not invoke the handler directly. Those lines record
|
|
|
114
132
|
names, statuses, durations, and parameter keys only — not results or error
|
|
115
133
|
bodies.
|
|
116
134
|
|
|
135
|
+
## Product defaults
|
|
136
|
+
|
|
137
|
+
These are not Joist-specific. Lint can catch some of them in handler code;
|
|
138
|
+
Agent Studio copy is on you.
|
|
139
|
+
|
|
140
|
+
- **No emoji** in copilot replies, cards, or handler strings. Put that in the
|
|
141
|
+
agent's Behavior instructions (MCP cannot write that field, so each new
|
|
142
|
+
action's Studio `instructions` must carry the line too).
|
|
143
|
+
- **Never send the user out of the host app.** No "open in <product>"
|
|
144
|
+
button and no pasted URLs — navigate with a navigation route, same tab.
|
|
145
|
+
- **Check row counts before choosing the experience.** On an empty account the
|
|
146
|
+
useful first action is one that creates data.
|
|
147
|
+
|
|
117
148
|
## Verification gates
|
|
118
149
|
|
|
119
150
|
Do not report success without all six:
|
|
120
151
|
|
|
121
|
-
1. TypeScript compiles with `
|
|
122
|
-
|
|
123
|
-
|
|
152
|
+
1. TypeScript compiles with `npm run typecheck` (`tsc --noEmit -p tsconfig.json`).
|
|
153
|
+
Do not run `npx tsc` — that can install the wrong package.
|
|
154
|
+
2. `foldspace lint` reports no errors (`foldspace build` runs this first).
|
|
155
|
+
3. The expected handler appears in `dist/index.js`.
|
|
156
|
+
4. The browser reports `inspect_registration:registration_ok`. For a named
|
|
124
157
|
action, the captured registry includes that handler.
|
|
125
|
-
|
|
158
|
+
5. The action behaves correctly against the real target workflow. Confirm
|
|
126
159
|
`[actions] local-handler:execute` in the attach log after the user exercises
|
|
127
|
-
the visible agent.
|
|
128
|
-
|
|
160
|
+
the visible agent. For a widget, that log must include `local-handler:render`
|
|
161
|
+
from a real chat turn — not a hand-built `render()` call.
|
|
129
162
|
6. Browser evidence came from the live target, not from hand-authored examples.
|
|
163
|
+
Neighbour fixtures still pass when they exist.
|
|
130
164
|
|
|
131
165
|
## Layout
|
|
132
166
|
|
|
133
167
|
- `agent/actions/` — one handler per action (`execute`, optional `render`),
|
|
134
168
|
registered in `index.ts`
|
|
135
169
|
- `agent/api/` — one HTTP helper per endpoint
|
|
136
|
-
- `agent/constants.ts` — agent, product,
|
|
137
|
-
|
|
170
|
+
- `agent/constants.ts` — agent, product, domain, plus empty `API_BASE` /
|
|
171
|
+
`AUTH_SOURCE` and `LOAD_MODE`
|
|
172
|
+
- `agent/utils.ts` — configure the harness runtime and re-export it. **This is
|
|
173
|
+
the only import surface for actions.**
|
|
138
174
|
- `foldspace.dev.json` — local harness target configuration
|
|
139
|
-
- `docs
|
|
175
|
+
- `docs/app-profile.md` — what is known about this app, and how it was established
|
|
140
176
|
|
|
141
177
|
Do not introduce another bundler or bundle format.
|
|
142
178
|
|
|
179
|
+
## What you already have
|
|
180
|
+
|
|
181
|
+
Import from `../utils`. Inspect that file before implementing another
|
|
182
|
+
general-purpose helper. MCP `generate_action_handler` may still emit a
|
|
183
|
+
skeleton that does not import it — fix the import when you implement.
|
|
184
|
+
|
|
185
|
+
| Helper | Use when | Do not use when |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| `apiFetch` / `apiFetchBinary` | The app's own JSON/file API, after a real 200 | Public marketing hosts (`publicFetch`); custom auth headers |
|
|
188
|
+
| `publicFetch` | Unauthenticated / marketing origin | Signed-in product APIs |
|
|
189
|
+
| `getAuthToken` / `parseJwt` | `AUTH_SOURCE` is `localStorage` or `cookie` | Custom header schemes — override `apiFetch` in `utils.ts` |
|
|
190
|
+
| `rankBy` | User typed a name; API search is exact or ignored | Domain ranking (invoices, coverage, MasterFormat) |
|
|
191
|
+
| `renderLoading` / `Empty` / `Error` / `Fatal` | Chatterblock empty/error paths | Branded cards — those stay in `agent/views/` |
|
|
192
|
+
| `getAgent` | Talking to Foldspace | You need a specific instance — then `agentIds()` |
|
|
193
|
+
| `armAllInstances` | Attach/bootstrap setup | Inside `execute` (can ship into `dist`; attach already arms test mode) |
|
|
194
|
+
| `redact` / `mapWithConcurrency` | Logging tokens; batching fetches | — |
|
|
195
|
+
|
|
196
|
+
If the observed auth is not Bearer + `localStorage`/`cookie`, stop re-exporting
|
|
197
|
+
that one function and keep the rest:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
export { getAgent, rankBy, renderEmpty } from "@foldspace_npm/harness/runtime";
|
|
201
|
+
export { apiFetch } from "./api";
|
|
202
|
+
```
|
|
203
|
+
|
|
143
204
|
## Agent learnings
|
|
144
205
|
|
|
145
|
-
|
|
146
|
-
not
|
|
206
|
+
`docs/app-profile.md` is the durable record of this app. Fill it as you probe,
|
|
207
|
+
not afterwards. Other notes in `docs/` are fine for session-specific lessons.
|
|
147
208
|
|
|
148
|
-
- Before similar work, read any
|
|
149
|
-
- After non-obvious discoveries, add
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
209
|
+
- Before similar work, read `docs/app-profile.md` and any other notes in `docs/`.
|
|
210
|
+
- After non-obvious discoveries, add them to the profile (or a short extra
|
|
211
|
+
note) covering verified gotchas, failed approaches, and how they were
|
|
212
|
+
established.
|
|
213
|
+
- Keep notes concise and evidence-based.
|
|
153
214
|
- Do not store secrets, cookies, HAR files, browser storage, or other transient
|
|
154
215
|
session data in `docs/`.
|
|
155
216
|
|
|
@@ -1,33 +1,28 @@
|
|
|
1
1
|
// Copy this file to <action_key>.ts and register it in index.ts.
|
|
2
2
|
// Do not register _example — it is not a real Agent Studio action.
|
|
3
|
+
//
|
|
4
|
+
// Import helpers from ../utils, not from @foldspace_npm/harness/runtime.
|
|
5
|
+
// The path below is a placeholder until you capture a real HTTP 200.
|
|
6
|
+
|
|
7
|
+
import { apiFetch, rankBy } from "../utils";
|
|
8
|
+
|
|
9
|
+
type Item = { id: string; name: string };
|
|
3
10
|
|
|
4
11
|
export const example_action = {
|
|
5
|
-
execute: async (params: {
|
|
6
|
-
const
|
|
7
|
-
if (!message) {
|
|
8
|
-
return { ok: false, error: "message is required" };
|
|
9
|
-
}
|
|
12
|
+
execute: async (params: { query?: string }) => {
|
|
13
|
+
const query = typeof params?.query === "string" ? params.query.trim() : "";
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const detail = error instanceof Error ? error.message : "unknown error";
|
|
15
|
-
return { ok: false, error: detail };
|
|
15
|
+
const result = await apiFetch<{ items?: Item[] }>("/__observe_me");
|
|
16
|
+
if (!result.ok) {
|
|
17
|
+
return { ok: false, error: result.error };
|
|
16
18
|
}
|
|
17
|
-
},
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// return;
|
|
28
|
-
// }
|
|
29
|
-
// const card = document.createElement("div");
|
|
30
|
-
// card.textContent = data.echo;
|
|
31
|
-
// host.append(card);
|
|
32
|
-
// },
|
|
20
|
+
const items = result.data.items ?? [];
|
|
21
|
+
const matches = rankBy(items, query, {
|
|
22
|
+
searchKeys: ["name"],
|
|
23
|
+
idOf: (item) => item.id,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return { ok: true, matches };
|
|
27
|
+
},
|
|
33
28
|
};
|
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
// Registration here is the switch: the SDK transmits these to the server, and
|
|
4
4
|
// the server excludes any active action it does not receive. Comment an entry
|
|
5
5
|
// out and the copilot can no longer see it — no unpublishing required.
|
|
6
|
+
//
|
|
7
|
+
// LOAD_MODE "injected" publishes the registry for attach to swap. "embedded"
|
|
8
|
+
// skips that assignment because the host page already loads Foldspace.
|
|
9
|
+
|
|
10
|
+
import { LOAD_MODE } from "../constants";
|
|
6
11
|
|
|
7
12
|
const actions = {};
|
|
8
|
-
|
|
13
|
+
|
|
14
|
+
if (LOAD_MODE === "injected") {
|
|
15
|
+
(window as any).__FOLDSPACE_REMOTE_ACTIONS__ = actions;
|
|
16
|
+
}
|
|
9
17
|
|
|
10
18
|
export default actions;
|
|
@@ -1,3 +1,28 @@
|
|
|
1
1
|
export const AGENT_API_NAME = {{AGENT_API_NAME_JSON}};
|
|
2
2
|
export const PRODUCT_ID = {{PRODUCT_ID_JSON}};
|
|
3
3
|
export const APP_DOMAIN = {{APP_DOMAIN_JSON}};
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Empty until you capture the app's own XHR. A guessed host looks like it
|
|
7
|
+
* worked. Include an explicit port if the app uses one.
|
|
8
|
+
*/
|
|
9
|
+
export const API_BASE = "";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Set after observing where the session token lives, for example
|
|
13
|
+
* `{ kind: "localStorage", name: "access_token" }` or
|
|
14
|
+
* `{ kind: "cookie", name: "session" }`.
|
|
15
|
+
*
|
|
16
|
+
* Custom headers that are not Bearer belong in a local `apiFetch` override —
|
|
17
|
+
* see CLAUDE.md. Do not guess this value.
|
|
18
|
+
*/
|
|
19
|
+
export const AUTH_SOURCE:
|
|
20
|
+
| { kind: "localStorage" | "cookie"; name: string }
|
|
21
|
+
| undefined = undefined;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* "injected" assigns `window.__FOLDSPACE_REMOTE_ACTIONS__` so attach can swap
|
|
25
|
+
* the bundle. "embedded" does not — the page already publishes handlers
|
|
26
|
+
* through its own SDK snippet.
|
|
27
|
+
*/
|
|
28
|
+
export const LOAD_MODE: "injected" | "embedded" = "injected";
|
|
@@ -1,15 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The seam between this repo and the harness page runtime.
|
|
3
|
+
*
|
|
4
|
+
* Actions import helpers from `../utils` (or `./utils`), never from
|
|
5
|
+
* `@foldspace_npm/harness/runtime` directly. Swapping an implementation —
|
|
6
|
+
* custom auth headers, a different `apiFetch`, branded loading UI — is a
|
|
7
|
+
* change to this file and nothing else. `configure` runs once at module load
|
|
8
|
+
* so `apiFetch` / `getAgent` can read tenant facts without importing
|
|
9
|
+
* `constants.ts` from the published package.
|
|
10
|
+
*
|
|
11
|
+
* ## What `export *` gives you
|
|
12
|
+
*
|
|
13
|
+
* HTTP (customer API, from the signed-in page):
|
|
14
|
+
* - `apiFetch` — JSON + `Authorization: Bearer`. Stop re-exporting this if
|
|
15
|
+
* the live app uses cookies, custom headers, or no Bearer (see below).
|
|
16
|
+
* - `apiFetchBinary` — same auth, raw bytes (file download).
|
|
17
|
+
* - `publicFetch` — no token; marketing / public origin only.
|
|
18
|
+
* - `getAuthToken` / `parseJwt` / `redact` — session read and safe logging.
|
|
19
|
+
* - `mapWithConcurrency` — cap parallel fetches over a list.
|
|
20
|
+
*
|
|
21
|
+
* Foldspace SDK (thin wrappers; the SDK is the source of truth):
|
|
22
|
+
* - `getAgent` — overlay handle for `searchInList` and similar.
|
|
23
|
+
* - `rankBy` — substring first, then SDK fuzzy match. Not for domain ranking.
|
|
24
|
+
* - `armAllInstances` — **attach/bootstrap only**, never from `execute`.
|
|
25
|
+
* Local `foldspace attach` already arms test mode. Importing this from an
|
|
26
|
+
* action can put those SDK calls in the production `dist` bundle.
|
|
27
|
+
*
|
|
28
|
+
* Widget chrome (unstyled; replace in `agent/views/` when brand matters):
|
|
29
|
+
* - `renderLoading` / `renderEmpty` / `renderError` / `renderFatal`
|
|
30
|
+
*
|
|
31
|
+
* Config (already called above; you rarely need these in an action):
|
|
32
|
+
* - `configure` / `getConfig`
|
|
33
|
+
*
|
|
34
|
+
* Leave `API_BASE` and `AUTH_SOURCE` empty in `constants.ts` until you capture
|
|
35
|
+
* a real XHR. Empty config makes `apiFetch` return `{ ok: false }` instead of
|
|
36
|
+
* guessing a host. Errors are returned, never thrown; show `result.error` in
|
|
37
|
+
* the widget and log `result.detail` only.
|
|
38
|
+
*
|
|
39
|
+
* ## Override example (custom auth — do not guess Bearer)
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* import { configure, apiFetch as defaultApiFetch, ... } from "@foldspace_npm/harness/runtime";
|
|
43
|
+
* // After configure(...):
|
|
44
|
+
* export async function apiFetch<T>(path: string, init: RequestInit = {}) {
|
|
45
|
+
* // observe the live request first, then copy its headers here
|
|
46
|
+
* return defaultApiFetch<T>(path, { ...init, headers: { ... } });
|
|
47
|
+
* }
|
|
48
|
+
* export { getAgent, rankBy, renderEmpty, renderError, renderFatal, renderLoading };
|
|
49
|
+
* // Do not `export *` if that would re-export the default apiFetch.
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
2
52
|
|
|
3
|
-
|
|
53
|
+
import { configure } from "@foldspace_npm/harness/runtime";
|
|
54
|
+
import { AGENT_API_NAME, API_BASE, AUTH_SOURCE } from "./constants";
|
|
4
55
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
56
|
+
configure({
|
|
57
|
+
agentApiName: AGENT_API_NAME,
|
|
58
|
+
apiBase: API_BASE,
|
|
59
|
+
authSource: AUTH_SOURCE,
|
|
60
|
+
});
|
|
9
61
|
|
|
10
|
-
|
|
11
|
-
apiName: AGENT_API_NAME,
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
return agent;
|
|
15
|
-
}
|
|
62
|
+
export * from "@foldspace_npm/harness/runtime";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# {{DISPLAY_NAME}} — app profile
|
|
2
|
+
|
|
3
|
+
Durable facts about `{{APP_DOMAIN}}` and its API, established by observing the
|
|
4
|
+
running app on **<date>** from a logged-in session.
|
|
5
|
+
|
|
6
|
+
**Everything here should be observed.** Each entry says how. Where something is
|
|
7
|
+
only believed, say so in the same breath — do not promote an assumption by
|
|
8
|
+
quoting this file. Re-run the probe before trusting an entry that matters.
|
|
9
|
+
|
|
10
|
+
Do not store secrets, cookies, HAR files, tokens, or session data here. Record
|
|
11
|
+
*where* the token lives and *what a redacted value looks like*, never the value.
|
|
12
|
+
|
|
13
|
+
How to re-run any probe below:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run inject
|
|
17
|
+
# Sign in. Capture from chrome-devtools MCP, or a page-context fetch,
|
|
18
|
+
# before foldspace attach owns the debug port.
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Start URL: `{{START_URL}}`
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Auth
|
|
26
|
+
|
|
27
|
+
- Where the token lives (cookie name, `localStorage` key, custom header), and
|
|
28
|
+
how you know — wrapping XHR/`fetch` on the live page beats guessing.
|
|
29
|
+
- Its lifetime, measured. (One app's expired in ~52 minutes and killed three
|
|
30
|
+
probing sessions.)
|
|
31
|
+
- What an expired or refused response looks like, verbatim. A CORS-less refusal
|
|
32
|
+
often surfaces as `TypeError: Failed to fetch` with no status — that is not
|
|
33
|
+
"host down".
|
|
34
|
+
- Anything inherited but never exercised — mark it **assumed**.
|
|
35
|
+
|
|
36
|
+
## API base
|
|
37
|
+
|
|
38
|
+
- The exact base, including any explicit port. Note if omitting `:443` fails
|
|
39
|
+
CORS, and whether the API is a **different host** from the app (a relative
|
|
40
|
+
path then returns the HTML shell).
|
|
41
|
+
|
|
42
|
+
## Account contents
|
|
43
|
+
|
|
44
|
+
Row counts from the signed-in account **before** choosing the experience. On an
|
|
45
|
+
empty account the only useful first action is one that creates data.
|
|
46
|
+
|
|
47
|
+
| Resource | Count | How established |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| | | |
|
|
50
|
+
|
|
51
|
+
## Endpoints called and observed
|
|
52
|
+
|
|
53
|
+
For each: the path, the **envelope**, the row keys you actually read, and how
|
|
54
|
+
you established them.
|
|
55
|
+
|
|
56
|
+
> Watch for endpoints on the same host that wrap the same resource
|
|
57
|
+
> differently (`{data}` vs `{contacts}` vs `{items}`). Reaching for the wrong
|
|
58
|
+
> key yields `undefined`, not an empty array — it throws downstream rather
|
|
59
|
+
> than returning nothing.
|
|
60
|
+
|
|
61
|
+
| Path | Method | Envelope | Verified |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| | | | |
|
|
64
|
+
|
|
65
|
+
## Verified behaviour
|
|
66
|
+
|
|
67
|
+
A table of probe → result. Paging, search, sort, filters. One row per call you
|
|
68
|
+
actually made.
|
|
69
|
+
|
|
70
|
+
**What the search matches.** Test prefix, mid-word, case, digits, across spaces,
|
|
71
|
+
and one transposed letter. Most app search is a plain case-insensitive substring
|
|
72
|
+
with no tolerance.
|
|
73
|
+
|
|
74
|
+
> A probe using values no record has proves nothing. If a filter returns zero,
|
|
75
|
+
> confirm a matching record exists before concluding anything.
|
|
76
|
+
|
|
77
|
+
| Probe | Result |
|
|
78
|
+
|---|---|
|
|
79
|
+
| | |
|
|
80
|
+
|
|
81
|
+
## Vocabulary observed
|
|
82
|
+
|
|
83
|
+
Real enum values seen on live records — not the generic ones you would expect.
|
|
84
|
+
Mark which you verified and which came from reading the app bundle.
|
|
85
|
+
|
|
86
|
+
## Identifiers
|
|
87
|
+
|
|
88
|
+
| Identifier | Produced by | Example |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| | | |
|
|
91
|
+
|
|
92
|
+
## Brand (sampled, not guessed)
|
|
93
|
+
|
|
94
|
+
Colour, typeface, greys, base size and radii from the running app's computed
|
|
95
|
+
styles — not from a screenshot. Re-sample per tenant; do not carry values
|
|
96
|
+
across.
|
|
97
|
+
|
|
98
|
+
## Endpoints seen in the bundle but NOT called
|
|
99
|
+
|
|
100
|
+
Listed so the next person knows they exist **and knows they are unverified**.
|
|
101
|
+
|
|
102
|
+
## Failures observed
|
|
103
|
+
|
|
104
|
+
| Call | Result | What it means |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| | | |
|
|
107
|
+
|
|
108
|
+
Record inconclusive results as inconclusive. A 401 on a dead session says
|
|
109
|
+
nothing about whether the endpoint works.
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
"scripts": {
|
|
8
8
|
"dev": "foldspace build --watch",
|
|
9
9
|
"build": "foldspace build",
|
|
10
|
+
"lint": "foldspace lint",
|
|
11
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
10
12
|
"inject": "foldspace inject",
|
|
11
13
|
"attach": "foldspace attach",
|
|
12
14
|
"attach:daemon": "foldspace attach --daemon"
|