Skip to content

fix(tools): carry the page query through appGetPage / appSendForm - #98

Merged
bazyk merged 3 commits into
developfrom
fix/page-query-param
Aug 24, 2026
Merged

fix(tools): carry the page query through appGetPage / appSendForm#98
bazyk merged 3 commits into
developfrom
fix/page-query-param

Conversation

@salimovartem

Copy link
Copy Markdown
Collaborator

Problem

A Smart Form page is stateless. A 302 answers {nextPage, query}, and the next page reads that
query back as body.query.* — that is how an app carries per-session state (a token, a card code)
across navigation.

Neither runtime tool accepted a query, so a logged-in page could not be rendered at all.
Driving one produced a cold page — empty fields, placeholders, an empty table — which reads as
a backend bug rather than a missing argument.

Change

  • appGetPage gains query, flattened into the URL query string exactly as the renderer
    sends it.
  • appSendForm gains query in the body, where the /send handler reads it.
  • New InQueryMap param kind (internal/tools/op.go). The keys are app-defined, so the tool
    takes one object and flattens it. Plain InQuery would have sent the whole object as a single
    opaque value, silently dropping the session. InQueryMap rejects a non-object with a clear
    error and skips blank keys / nil values — a nil would otherwise reach the backend as the literal
    string "null".
  • buttonId / buttonData descriptions now state the submitOnChange behaviour: the changed
    field's id arrives as buttonId, and only select populates buttonData (radio / check /
    toggle / edit send {}, so the value is read from data).
  • simulator-smart-forms-runtime documents carrying resp.query forward across navigation,
    with the ✅ / ❌ pair.
  • Drive-by: .DS_Store added to .gitignore (separate commit).

Tests

Four new subtests in internal/tools/smartforms_test.go:

  • the query object is flattened into distinct URL keys, and does not leak as a single query=
    key;
  • a non-object query returns an error result instead of being silently stringified;
  • a blank key and a nil value are dropped, and nothing else is;
  • appSendForm carries query in the request body.

Verification

make build, make vet, go test ./... (whole module) — all green. make discovery produces no
drift (no skill frontmatter changed).

@gh-corezoid

Copy link
Copy Markdown
Contributor

AI Review

Adds query support to appGetPage (flattened into the URL query string via a new InQueryMap param kind) and appSendForm (embedded in the request body), fixing the platform's own session-carrying pattern for stateless Smart Form navigation; also documents the flow in the skill and adds four targeted unit tests.

Checklist

Check Result
U1 — Conventional commit format ✅ pass
U2 — No leaked credentials ✅ pass
U3 — No merge commits ✅ pass
U4 — PR targets correct base branch (develop) ✅ pass
U5 — Build & tests (Go) ✅ pass
U6 — Architectural & design consequences ✅ pass
S1 — No manual edits to public/ ✅ pass
S2 — API path parameter names match papi-openapi.json ✅ pass
S3 — New tools have eval scenarios ✅ pass
S4 — Discovery artifacts committed if source changed ⏭️ skip
S5 — All six manifest files version-synced ⏭️ skip
S6 — README.md / ARCHITECTURE.md updated for new tools ✅ pass

Details

U1 — Both commits follow the convention correctly.

  • chore(gitignore): ignore .DS_Store (34 chars) ✅
  • fix(tools): carry the page query through appGetPage / appSendForm (65 chars) ✅
    Neither touches a version manifest so no "bump to X.Y.Z" suffix is needed.

U5 — Verified by checking out the PR branch in a disposable clone:

  • go build ./... in plugins/simulator/mcp-server → exit 0 ✅
  • go test ./... on PR branch → all 14 packages green (including internal/tools with the 4 new subtests) ✅
  • go test ./... on base develop → all green ✅ — no regressions introduced.

S2 — Path params in both ops (accId, ref, envTitle, page) match papi-openapi.json exactly. The new query param in appGetPage uses InQueryMap (dynamically flattened into the URL query string, not a path segment) and the new query in appSendForm is InBody — neither introduces any path parameter names. ✅

S3 — No new tool functions were added; appGetPage and appSendForm already have eval scenarios (4 references confirmed). ✅

S4internal/tools/op.go and internal/tools/smartforms.go changed, but public/ is absent from the diff. The PR author states make discovery produces no drift (no skill frontmatter changed). Cannot independently run discovery from the diff; marking skip. If CI runs make discovery, that covers it.

S5 — No manifest files (plugin.json, marketplace.json, POWER.md) appear in the diff → skip.

Issues found

No issues found.


This review was generated automatically. A human maintainer should still make the merge decision.

@bazyk

bazyk commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Review vs pong-server / control-cdu

appGetPage ✅ — query as InQueryMap flattens into the URL; pong-server getScriptPage reads req.query and forwards it as body.query to /get (src/packages/controlMain/api/applications/pages.ts:312). Correct.

appSendForm ⚠️ the query never reaches the backend as written. It's declared In: InBody, but pong-server's sendScriptPage reads query from the URL, not the JSON body, and spreads it after ...body:

// pages.ts:382
const { body, query } = req;          // query === req.query (URL string)
// pages.ts:415
body: { ...body, query, context },    // req.query OVERWRITES body.query

Since appSendForm is a POST with no URL query string, req.query is {} and it clobbers the query we put in the body → the backend /send handler receives body.query = {}. The per-session token/record-id this PR aims to carry is dropped.

The real client confirms the contract: control-cdu handleSubmitForm calls apiRequest({method:POST, path:/pages/..., query, data:{...}}) and apiRequest appends query to the URL (/api/1.0${path}${queryString}) — the body has no query key (control-cdu/src/utils/apiRequest.ts, src/store/page/sagas.ts:529).

Fix: change appSendForm's query to In: InQueryMap (same transport as appGetPage). The description ("handler receives it as body.query.*") stays accurate — only the transport is wrong. The test appSendForm carries query in the body should assert it lands in the URL instead.

The buttonId / buttonData doc clarifications are accurate. Holding merge until the transport is fixed.

salimovartem added a commit that referenced this pull request Aug 24, 2026
Review catch (PR #98): pong-server's `sendScriptPage` reads `query` from the
URL and spreads it AFTER the body —
`body: { ...body, query, context }` (controlMain/api/applications/pages.ts:415)
— so `req.query` overwrites whatever the caller put in `body.query`. On a POST
with no query string `req.query` is `{}`, which means the body-borne query was
silently clobbered and the per-session token/record id never reached the
process. The real client (control-cdu `handleSubmitForm`) confirms the
contract: it appends `query` to the URL and sends no `query` body key.

- `appSendForm`'s `query` moves from InBody to InQueryMap — same transport as
  appGetPage. The description stays accurate: the handler still forwards it to
  the process as `body.query.*`; only the wire slot was wrong.
- The subtest now asserts the query lands in the URL and that no `query` key is
  sent in the body (it would be overwritten anyway, so it would only mislead).
- op.go's InQueryMap doc, the runtime SKILL.md and the CHANGELOG entry drop the
  "appSendForm puts it in the body" claim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@salimovartem

Copy link
Copy Markdown
Collaborator Author

Good catch — confirmed against pong-server and fixed in 37b675e.

sendScriptPage destructures query from req (so req.query, the URL) and spreads it after the body:

// controlMain/api/applications/pages.ts:375, 415
const { body, query } = req;
body: { ...body, query, context },   // req.query wins over body.query

On a POST with no query string Fastify gives req.query === {}, so the body-borne query was overwritten and the session never reached the process. Neither postFormSchema nor getPageSchema declares a querystring, so unvalidated app-defined keys pass through on both routes — the URL is the right slot for both.

Changes:

  • appSendForm's query is now In: InQueryMap, same transport as appGetPage. The description is unchanged in substance (the handler still forwards it as body.query.*) — only the wire slot moved.
  • The subtest is now appSendForm carries query in the URL, not the body: asserts token=tok lands in the URL, that the object doesn't leak as a single query= key, and that no query key is sent in the body (it would be clobbered anyway, so sending it would only mislead a reader).
  • Dropped the "appSendForm puts it in the body" claim from the InQueryMap doc comment in op.go, the simulator-smart-forms-runtime SKILL.md paragraph, and the CHANGELOG entry — all three now say both tools use the URL, and the op.go comment records why (req.query overwrites body.query).

make build, make vet, go test ./... green; make discovery produces no drift.

@gh-corezoid

Copy link
Copy Markdown
Contributor

AI Review

Re-review after synchronize: fixes appGetPage/appSendForm session-query transport, adds InQueryMap param kind and four new tests — now fully correct with both tools sending query via URL query string.

Checklist

Check Result
U1 — Conventional commit format ✅ pass
U2 — No leaked credentials ✅ pass
U3 — No merge commits ✅ pass
U4 — PR targets correct base branch (develop) ✅ pass
U5 — Build & tests (Go) ✅ pass
U6 — Architectural & design consequences ✅ pass
S1 — No manual edits to public/ ✅ pass
S2 — API path parameter names match papi-openapi.json ✅ pass
S3 — New tools have eval scenarios ✅ pass
S4 — Discovery artifacts committed if source changed ✅ pass
S5 — All six manifest files version-synced ⏭️ skip
S6 — README.md / ARCHITECTURE.md updated for new tools ✅ pass

Details

U1 — All three commits follow the convention:

  • chore(gitignore): ignore .DS_Store (34 chars) ✅
  • fix(tools): carry the page query through appGetPage / appSendForm (65 chars) ✅
  • fix(tools): send appSendForm's query on the URL, not in the body (63 chars) ✅
    None touch a version manifest, so no "bump to X.Y.Z" suffix required.

U5 — Verified in a disposable clone of the PR branch (fix/page-query-param):

  • go build ./... in plugins/simulator/mcp-server → exit 0 ✅
  • go test ./... on PR branch → all 14 packages green (including internal/tools with all new subtests) ✅
  • go test ./... on base develop → all green ✅ — no regressions introduced.

S2 — Path params (accId, ref, envTitle, page) unchanged. The new query param in both appGetPage and appSendForm uses InQueryMap (flattened into URL query string) — not a path segment. No path parameter name issues. ✅

S3 — No new tool functions added; appGetPage and appSendForm already have eval scenarios. ✅

S4internal/tools/op.go and internal/tools/smartforms.go changed. Ran make discovery (go run ./cmd/gendiscovery --root ../../..) on the PR branch locally — no drift in public/ (19 skills written, git diff public/ empty). ✅

S5 — No manifest files (plugin.json, marketplace.json, POWER.md) in the diff → skip.

S6 — No new tools added or renamed; only existing appGetPage/appSendForm gained a new optional parameter. README and ARCHITECTURE.md tables list tools by name, not parameters — no update required. ✅

Issues found

No issues found.

Fixed since last review ✅

  • appSendForm query transport mismatch — In the initial review the query parameter was InBody, but pong-server's sendScriptPage reads query from the URL and overwrites any body-borne value (body: { ...body, query, context } where query = req.query). This was flagged by a human reviewer. The new commit (37b675e) moves appSendForm's query to InQueryMap (same transport as appGetPage), and updates the corresponding subtest to assert the query lands in the URL query string and is absent from the request body. CHANGELOG and SKILL.md documentation drops the incorrect "appSendForm puts it in the body" claim. Fully resolved. ✅

This review was generated automatically. A human maintainer should still make the merge decision.

salimovartem and others added 3 commits August 24, 2026 11:27
macOS drops .DS_Store into every directory that gets opened in Finder, and
they were showing up as untracked noise in `git status` across the plugin
tree. Ignore them repo-wide so they stop polluting diffs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Smart Form page is stateless: a `302` answers `{nextPage, query}` and the
next page reads that query back as `body.query.*`. That is how an app carries
per-session state (a token, a card code) across navigation. Neither runtime
tool accepted a query, so a logged-in page could not be rendered at all —
driving one produced a cold page that read as a backend bug.

- `appGetPage` gains `query`, flattened into the URL query string exactly as
  the renderer sends it.
- `appSendForm` gains `query` in the body, where the `/send` handler reads it.
- New `InQueryMap` param kind in `internal/tools/op.go` does the flattening.
  Plain `InQuery` would have sent the whole object as one opaque value,
  silently dropping the session; `InQueryMap` rejects a non-object and skips
  blank keys / nil values (a nil would otherwise render as the literal
  "null").
- `buttonId` / `buttonData` descriptions now state the submitOnChange
  behaviour: the changed field's id arrives as `buttonId`, and only `select`
  populates `buttonData`.
- `simulator-smart-forms-runtime` documents carrying `resp.query` forward.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review catch (PR #98): pong-server's `sendScriptPage` reads `query` from the
URL and spreads it AFTER the body —
`body: { ...body, query, context }` (controlMain/api/applications/pages.ts:415)
— so `req.query` overwrites whatever the caller put in `body.query`. On a POST
with no query string `req.query` is `{}`, which means the body-borne query was
silently clobbered and the per-session token/record id never reached the
process. The real client (control-cdu `handleSubmitForm`) confirms the
contract: it appends `query` to the URL and sends no `query` body key.

- `appSendForm`'s `query` moves from InBody to InQueryMap — same transport as
  appGetPage. The description stays accurate: the handler still forwards it to
  the process as `body.query.*`; only the wire slot was wrong.
- The subtest now asserts the query lands in the URL and that no `query` key is
  sent in the body (it would be overwritten anyway, so it would only mislead).
- op.go's InQueryMap doc, the runtime SKILL.md and the CHANGELOG entry drop the
  "appSendForm puts it in the body" claim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bazyk
bazyk force-pushed the fix/page-query-param branch from 37b675e to 1d1446a Compare August 24, 2026 08:30
@bazyk
bazyk merged commit 90eb369 into develop Aug 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants