Skip to content

fix(nextjs-template): flow-aware degradation + api-<sub> endpoint + scoped tsconfig#25

Draft
yyyyaaa wants to merge 3 commits into
mainfrom
feat/blocks
Draft

fix(nextjs-template): flow-aware degradation + api-<sub> endpoint + scoped tsconfig#25
yyyyaaa wants to merge 3 commits into
mainfrom
feat/blocks

Conversation

@yyyyaaa

@yyyyaaa yyyyaaa commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

What

Makes the nextjs/constructive-app template scaffold cleanly when consuming Constructive Blocks under a minimal auth preset — the template half of the cross-repo Blocks effort (library/docs constructive-io/dashboard#227, skill constructive-skills, harness constructive-sdk-agentic-flow).

Contents (single commit 0c064ff)

  • Flow-aware admin-compat degradation — the template hard-imports org/invite/member admin-SDK ops that don't exist under the auth:email preset, hard-failing tsc+next build out of the box. A compat shim lets the scaffold compile under both auth:email and b2b.
  • App data endpoint → api-<sub> — point codegen + runtime at api-<sub> (with a CODEGEN_APP_HOST seam) instead of the dead app-public-<sub> Host.
  • Scoped tsconfig — so next build type-checks the app, not unrelated sibling workspace packages.

Status

Draft / WIP. Unblocks the Blocks on-ramp scaffold; in repeated harness runs 8–10 of every fleet's agents hand-wrote these same fixes, so landing them in the template removes that per-build friction. Follow-up: harden the b2b-import degradation (the compat shim currently lives inside the codegen-regenerated dir and must be re-applied after pnpm codegen).

🤖 Generated with Claude Code

yyyyaaa and others added 3 commits June 1, 2026 17:44
… + scoped tsconfig

Make a freshly-scaffolded constructive-app green-build for a minimal auth:email
flow with NO manual stubbing, while keeping the b2b org surface fully functional
when the admin SDK has it.

F1 — degrade org/invite/members/settings when the admin SDK lacks Org*/Invite*:
- Add src/lib/gql/admin-compat.ts: re-exports @sdk/admin and, for each symbol that
  exists only in the b2b admin SDK, exports a value that resolves to the REAL
  export when present (sdk[name] ?? stub). Lives outside src/graphql/sdk so codegen
  (which wipes the generated SDK dirs) never clobbers it.
    - auth:email build → org/invite symbols absent → typed stubs keep the dead
      template code type-checking; they are never on the runtime path and throw
      loudly if invoked.
    - b2b build → symbols present → genuine hooks/fns are used at runtime.
- Repoint the 13 org/invite/app hook + members-route imports of the 23 at-risk
  symbols from @sdk/admin to @/lib/gql/admin-compat.
- Annotate 5 derived id arrays as string[] so the where:{id:{in:…}} sites stay
  typed when the upstream fetch degrades to any (no-op cast on the b2b path).
- Verified by overlaying the full template src against BOTH a real auth:email SDK
  (org symbols absent) and a real b2b/org-roles SDK (org symbols present):
  tsc --noEmit = 0 errors in both.

F2 (template half) — app DATA endpoint is api-<sub>, not app-public-<sub>:
- graphql-codegen.config.ts: the app target Host now honors CODEGEN_APP_HOST and
  defaults to api-{db}.localhost:3000; endpoint + Host header stay in sync (Host
  drives routing, so a URL-only override still 404s). admin/auth gain matching
  CODEGEN_*_HOST overrides.
- config-core.ts getAppEndpoint(): default resolves to http://api-{db}.localhost,
  still overridable via NEXT_PUBLIC_APP_ENDPOINT.

F11 — scope tsconfig:
- include limited to src/** (+ Next types); add "packages" to exclude so the
  provision sub-package isn't greedily compiled by the app's tsc --noEmit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ecipes

BASE tier: strip all org/b2b code so a base app builds clean on the
auth:email module set with zero dangling org imports.

#3b — tier the template (base = auth:email only):
- delete org/invite/app-admin surfaces: app/{organizations,orgs,invite,
  invites,users,settings}, components/{organizations,members,invites,
  settings}, lib/gql/hooks/admin/{organizations,policies},
  admin/app/{use-app-invites,use-app-settings,use-app-users},
  invites-shared-utils, auth/use-submit-{invite,org-invite}-code
- delete the 111-line lib/gql/admin-compat.ts shim (no consumer left)
- delete orphan org demo scaffolding (components/header.tsx,
  layouts/{navigation-data,sidebar-config}.ts) + auth invite-screen
- simplify shared layer to a single app-root level: app-routes (drop
  org/app routes + buildOrgRoute), navigation (sidebar-config,
  use-entity-params, use-sidebar-navigation), authenticated-shell (drop
  org switcher), route-guards (drop invite-token branch), admin/auth
  barrels, app-permissions; home page drops the orgs/members advert
- relocate query-string helpers to lib/navigation/query-string.ts
- make TopBarConfig.entityLevels optional (b2b populates it)
- base seed only seeds users (no orgs)
- B2B OPT-IN: docs/B2B.md documents re-adding org via the registry org
  blocks (org-create-card/org-members-list/org-roles-editor/
  org-settings-form) + provisioning ORG_MODULES

#3a — provision recipe fixes:
- create-db.ts: default to the auth:email preset via new modules.ts
  (AUTH_EMAIL_MODULES, mirrors flows.json email-password); scoped modules
  use TUPLE form (colon-string is rejected: PROVISION-001)
- provision.ts: bake in the users self-update control-plane step
  (AuthzDirectOwner self_update policy on users_public.users) and a
  provisionAppTable() blueprint helper with object-form grants + the
  AuthzDirectOwner owner-scoped default
- graphql/provision/provision.config.ts: replace modules:['all'] with the
  auth:email tuple set

Verified: tsc --noEmit clean on the provision package (real
@constructive-io/node) and on the base src (SDK call sites stubbed loose;
all local/@-alias imports resolve; zero refs to deleted paths/symbols/
routes). Full next build needs a provisioned DB (later stress-test).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ision recipes

Fix the baked provision.ts bug cluster the v5 stress-test caught (all 5 agents
hit these), verified end-to-end against the live hub.

B1 (split-endpoint): provision used one client (api.localhost) for everything,
so createDatabaseProvisionModule / createBlueprint / constructBlueprint /
createSecureTableProvision failed with 'Unknown type ...Input' — those live on
modules.localhost. Add config.modulesEndpoint (env PROVISION_MODULES_ENDPOINT /
PG_PROVISION_MODULES_ENDPOINT) + helpers.createModulesClient, and route each
call to the right host: blueprint / construct / secure-table / provision-module
mutations -> modules; apis/schemas/tables/databases reads + createApiSchema ->
api. Verified by introspecting both hosts.

B2 (api-name): schema-attach looked up the app-data API by name 'app', but the
hub names it 'api' (services_public.apis ... name='api'), so the step silently
no-op'd ('App API not found'). Look up 'api' and make the step non-fatal /
idempotent (constructBlueprint already auto-attaches the table's schema).

B3 (field-shape + PK + policy): provisionAppTable emitted bare-string field
type/default -> constructBlueprint BAD_FIELD_INPUT (table never created). Emit
FieldType/FieldDefault OBJECTS (type {name:'text'}, default {value:false});
PREPEND DataId to the default nodes so the table gets a uuid PK (without it no
update/delete); and use AuthzDirectOwner data.entity_field:'owner_id' (the key
the policy actually reads) instead of owner_field. Ship the base `todos` table.

B4 (cross-tenant SQL): membership-defaults + SEED_SCHEMA detection used a
floating `LIKE '%memberships_public' DESC LIMIT 1` with no db filter, which on
the shared hub resolves to a SIBLING tenant (data-corruption risk). Resolve THIS
tenant's physical schema via the metaschema (scoped by database_id) through a new
helpers.resolvePhysicalSchemaName; same fix applied to the create-db admin grant.

Quick wins: fix stale 'app-public-{db}' comments + point appEndpoint at the
api-{db} data host (matches codegen); make the dev/start port env-overridable
(${PORT:-3011}); document in B2B.md the org-create RLS prerequisite (createUser
type=2 needs the create_entity app-permission bit 0x10000 or it hits 'new row
violates RLS for table users'), how create-db grants it, and the
@constructive-io/node *.localhost 'fetch failed' workaround (NodeHttpAdapter /
@constructive-io/fetch createFetch).

Live smoke (hub, constructive-db 3e2000b644): provisioned throwaway tenant
fixsmoke_79faay (auth:email) with the fixed create-db (modules endpoint) +
provision. todos table created in <tenant>_app_public with id PK + owner_id (NO
BAD_FIELD_INPUT), owner-scoped RLS on owner_id, users self_update policy landed
(id = current_user_id), is_done default false from {value:false}; B4 resolved
this tenant's exact schemas (the old query would have hit sibling
rlsb4-f3891e-7073ae68); api-fixsmoke_79faay.localhost serves the todos type.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant