Repository navigation
Typed API client (openapi-fetch + generated types) and typed Inertia page props (#504 #505) - #576
Merged
Merged
Conversation
…top teaching the cast (#505) Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…remove double casts (#505) Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…rorToast (#504) createApi<paths>() wraps openapi-fetch with same-origin credentials, Accept: application/json and the csrfToken shared prop as X-CSRF-Token on unsafe methods. apiResult()/apiFetch() normalise to { ok, status, data, detail, fieldErrors, error }, network failures as status 0. openapi-typescript is pinned as a packages/ui devDependency so it can use TypeScript 5 without displacing the repo's TypeScript 7. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
Anonymous pages (and therefore any page a module may mark cacheable) never carry it, and nothing is minted into their session. API-token principals render as anonymous and get none either. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
#504) Cuts each module's /api operations (and only the component schemas they reach) out of the in-memory schema and runs openapi-typescript into <pkg>/pages/api.generated.ts. Without --module it regenerates modules that already have the file; it never writes outside the project. Operations whose JSON success body has no schema are listed as untyped. ci-api-types-fresh hashes the generated files before/after like ci-i18n-fresh; it runs in make lint and the js-typecheck CI job. *.generated.ts is exempt from Biome and the 300-line cap. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…504) tenants: 10 fetch calls move to api.* (8 typed, 2 through apiFetch: the settings module's route and the server-supplied multipart upload URL); useTenantErrors.describe takes an ApiResult and keeps each call's own offline copy. apiDetail.ts is gone. api_tokens: postJson becomes typed api.POST calls that still pin the page's csrf_token prop; the unused api_url prop is dropped. Pages take typed component args instead of the usePage() double cast. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
apiFetch and the createApi middleware attached X-CSRF-Token whatever the destination, so an absolute or protocol-relative URL (or a foreign baseUrl) would hand the session's token to that origin. The header is now set only when the request's origin equals location.origin. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
Module templates (base and framework_repo) add pages/api.ts, a sample pages/api.generated.ts matching what gen-types emits for the CRUD routes (pinned by test_cli_gen_types_scaffold), generated DTO types for page props, and a Browse delete action through api.DELETE. make new-module regenerates the types once the module is installed; smpy create-module prints the gen-types command (the module is not importable yet there). Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
Untyped /api endpoints (JSON success body without a schema), 15 in all:
AuditLog: GET /api/audit_log/export.csv
Branding: GET /api/branding/logo, /logo-dark, /favicon
FileStorage: GET files/{id}/thumbnail, files/{id}/download,
public/{id}/thumbnail, public/{id}/{filename}, public/{id}
Users: DELETE auth/token; POST auth/forgot-password, auth/reset-password,
auth/request-verify-token; GET auth/{provider}/login, /callback
Details in docs/superpowers/specs/2026-10-22-untyped-endpoints.md.
Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…fter an anonymous navigation (#504) Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…che embedded parse (#504) Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
… of double casts) Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Deploying simple-module-python with
|
| Latest commit: |
6cdaf19
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://55fd989c.simple-module-python.pages.dev |
| Branch Preview URL: | https://feat-typed-client-b4.simple-module-python.pages.dev |
19 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Batch 4 adds a shared typed API client generated from the OpenAPI export (#504) and typed Inertia page props (#505). Design:
docs/superpowers/specs/2026-10-22-typed-client-props-design.md.What changes
#504 Typed API client
@simple-module-py/uiclient:createApi<paths>(): anopenapi-fetchclient.apiResult(...)normalises a call's result to{ok, status, data, detail, fieldErrors, error}.apiFetchcovers multipart uploads and non-/apiroutes.useApiErrorToast(fallbackKey)is opt-in. The client itself contains no UI strings.csrfToken. Only signed-in sessions get it, and it is resolved lazily when an Inertia page renders, so JSON API calls and downloads never create one.X-CSRF-Tokenon unsafe methods, and only to same-origin URLs.RequiresCsrf).smpy openapi gen-types [--module X]writes a committed<pkg>/pages/api.generated.tsper module.make gen-api-typesandci-api-types-fresh(the latter inmake lintand the CI js-typecheck job).*.generated.tsfiles are exempt from Biome and the 300-line cap.openapi-typescriptis a devDependency ofpackages/ui, because it needs TypeScript 5. Scaffolded apps fall back tonpx.apiFetch(one calls the settings module's endpoint, the other is a multipart logo upload).api_urlprop is removed (a behaviour change, noted in the CHANGELOG).pages/api.tsplus the generated types, and pages useapi.*with typed props.make new-moduleruns gen-types. Skills and docs are updated./apiendpoints still have no response type, mostly file downloads and redirects. They are listed indocs/superpowers/specs/2026-10-22-untyped-endpoints.md.#505 Typed page props
usePageProps<T>()hook.SharedProps(auth, menus, i18n, csrfToken, nativePrefixes) is registered through Inertia'ssharedPagePropsaugmentation.// cast-ok: <reason>.ci-check-no-double-cast, run inmake lintand the CI python-lint job. It covers module frontend dirs,packages/uiandhost/client_app, and excludes tests.docs/frontend/shared-props.mdno longer teaches the cast.Verification (after merging main with batches 2 and 3)
make lint✓make test-py: 5590 passed ✓make test-js: 708 passed ✓make ci-doctor✓make ci-api-types-fresh✓uv lock --check✓/ship
The rest of the fetch migration is next: users, background_tasks, branding, settings and file_storage, plus a check that rejects raw
fetch()in pages. Page props generated from Python DTOs will be a follow-up issue.Closes #505
Part of #504; it stays open for the rest of the fetch migration.
https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4