Skip to content

web: placement review page - #15

Closed
SamGu-NRX wants to merge 10 commits into
t3/serverfrom
t3/web-review
Closed

SamGu-NRX wants to merge 10 commits into
t3/serverfrom
t3/web-review

Conversation

@SamGu-NRX

@SamGu-NRX SamGu-NRX commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Parked (2026-09-26 morning). The product is the iOS app. This page shows the server's answer in a browser. It was useful for checking the server before the app could upload, but it isn't on the path to shipping. It stays a draft until the team decides whether an installer-facing view belongs in the demo.

The placement result on the web: a single page that sends a scan to the placement server and shows the answer. A Base reviewer or homeowner can read it in a few seconds.

Stacked on #11 (base t3/server), so the page's copy of the result schema is tested against the server's in CI. Merge #11 first; this PR's own changes are in web/ and .github/workflows/web.yml.

Live preview: https://house-scanning-p56ke670g-sam-gus-projects-7a4b6082.vercel.app, deployed at 1ebc0f4. Previews build with VITE_PLACEMENT_API=https://house-scanning-server.vercel.app, the hosted placement server (public rules under its labelled demo policy, #11), so they answer live with no laptop. Git auto-deploy is off for this project, so previews are deployed by hand when the page changes.

The preview at ef15f1e answering live from the hosted server: Needs review for the walk that stopped at a corner, with two views to add, the first saying how far out from the wall the ground must be seen

What changed

A new reviewer page in web/. It is one page with no routing or state library.

  • Scene in. Pick one of three bundled synthetic samples, or drop a scene.json or zip bundle. The page posts it to POST /v1/placements on the placement server (server: placement solver, rules and API (S2) #11).
  • Answer out, in reading order:
    • an inspector's stamp for the decision (Fits, Needs review, No spot) and the server's one-sentence summary. Under rules not approved for automatic decisions, the stamp always reads Needs review;
    • where the battery goes, the cable run with its error, and how many checks pass;
    • the server's site plan, which fills in after the answer (see below);
    • the checks that need a look, each with its measurement, limit and reason;
    • the views still missing, each saying how far the view must reach when the server names a depth;
    • every check, folded away.
  • Every state is real. The loading bar appears only if the answer takes longer than 150 ms. A server refusal names the field at fault. An unreachable server says what to run or where to point the page. An HTTP error that isn't in the server's format (a 504 from the host, a 404 from another site) shows its status instead of calling the server unreachable. A file over 4.5 MB is refused before sending (below). An http:// server entered on the https page gets a warning, because the browser will block it. With no server, a sample can show the answer the server gave when it was recorded, labelled "Saved answer".
  • One upload for a refused scene. The page posts the scene for the answer first and asks for the site plan only after an answer arrives; the plan shows "Drawing the site plan…" until then. A refused or oversized scene is sent once. An answered one is sent twice, because the server has no endpoint that returns both; scene.json is small, so the team kept it that way.
  • Server address. It comes from VITE_PLACEMENT_API, or /api, which pnpm dev and pnpm preview proxy to a local server. A reviewer can change it in the page. ?sample=<id> opens a sample, so a link can share an answer. The address can't be set from a link, so a link can't send someone's scan to another server.

How the result types stay in sync

src/contract/result.schema.json is a byte-identical copy of server/schemas/result.schema.json, most recently updated for missing_evidence[].out_ft (ef15f1e). pnpm contract generates two files from it: result.ts (the types) and validate-result.js, a standalone validator. Every answer is validated at runtime, so a server that breaks the contract shows a specific error ("at /decision: must be equal to one of the allowed values") instead of a broken page.

The generated files accept fields the copy doesn't know, as long as schema_version has the same major version (1.x), so a server that adds a field doesn't break a deployed page; the iOS app does the same. An answer from schema 2 is refused at /schema_version. Only the generated files are loosened (tolerantSchema in scripts/contract-lib.ts); the copy stays identical to the server's.

scripts/contract.test.ts fails when the generated files don't match the copy, or the copy doesn't match the server's schema. Because this PR is stacked on #11, server/schemas is present and the drift test always runs; a one-byte change to the server schema makes it fail. The Web workflow also runs when server/schemas/** changes. The bundled samples' saved answers are recorded under the public rules the hosted server uses, and a test checks that.

UI changes

A new page, so the captures are after-only. Since #11's demo policy (cd0da8b), the hosted server decides automatically and labels each answer as demo rules, so the samples read Fits, Needs review and No spot. Captured from the preview at 1ebc0f4, 1280 px wide:

Fits No spot
Fits: the battery 2 ft 9 in right of the meter, every check passes, summary ending "Demo rules: public values and placeholders (pool 10 ft, drive 5 ft), not Base's." No spot: every spot fails opening clearance, gas clearance and route path; the garage door is 0 ft from the battery, labelled as demo rules

The captures below are from a7c9b12, when the hosted server's public rules sent every answer to a person; the layout hasn't changed since. Desktop is 1280 px wide; phone is 390 px (iPhone 12 Pro).

Needs review, light Needs review, dark
Needs review: the walk stopped at a corner, 11 of 13 checks pass, areas not seen hatched The same answer in dark mode
Every check passes, still Needs review A file too large to send
Needs review: the battery fits 2 ft 9 in right of the meter, 13 of 13 checks pass, rules not approved for automatic decisions Too large to send: capture-with-photos.zip is 5.0 MB; send scene.json on its own
Phone Phone, server unreachable (27f3dbd, unchanged)
Phone: garage in the way, Needs review, a person must confirm before rejecting Phone: can't reach the placement server, with Try again and Show the saved answer

Verification

At 1ebc0f4, from web/:

  • pnpm check (lint, typecheck, test, build): passes, with 74 tests and none skipped; CI runs it, including the contract drift test. Tests cover the size refusal before sending, one request per refused scene, HTTP errors apart from network errors, answers with added fields under 1.x, the mixed-content check, the stamp under unapproved rules, and that every saved answer was recorded under the hosted server's demo policy with the decision it gives (pass, manual review, reject).
  • The preview at 1ebc0f4, in a browser: live answers from the hosted server for all three samples (Fits, Needs review with two views to add and their depth, No spot), each labelled as demo rules.
  • At a7c9b12, against the hosted server: a 5 MB zip refused with no request sent while the server stays "Connected"; the plan requested only after the answer finished. From outside, a 5 MB POST gets Vercel's 413 FUNCTION_PAYLOAD_TOO_LARGE with no access-control-allow-origin, which is why the page refuses such files itself.
  • Earlier checks at c54af22 stand for the unchanged parts: a keyboard-only pass, Lighthouse mobile (performance 94, accessibility 100, best practices 96), and the prep-for-prod review.
  • Not checked: the mixed-content warning in a browser (its condition is unit-tested), and a physical phone.

Risk

  • Vercel refuses request bodies over 4.5 MB before they reach the server, without CORS headers, so the page refuses such files itself. A phone bundle with its keyframe JPEGs can exceed the limit; send scene.json alone.
  • The preview doesn't redeploy on push; it shows 1ebc0f4 until someone deploys it again.
  • Machine-style reasons. Check reasons come from the server verbatim. Some name objects the way the scene does (objects[2] garage_door). Fixing that wording belongs to the server.

Checklist

  • The scope is focused; related changes belong together
  • I explained what changed and why
  • I included before/after images for visible changes (new page, after-only)
  • N/A: video. The only motion is the stamp and a short reveal; nothing is interactive beyond clicks.

🤖 Generated with Claude Code

One page that sends a scene (a bundled sample, or a dropped scene.json
or zip) to the placement server's POST /v1/placements and shows the
answer: an inspector's stamp for the decision, the spot and cable run,
the server's site plan, every check with its reason, and the views
still missing. Loading, refusals and an unreachable server are their
own states; with no server, a sample can show the answer recorded for
it. The server address is configurable (VITE_PLACEMENT_API, a dev
proxy at /api, or Change in the page) and ?sample= links to a sample.

The result types are generated from a vendored copy of
server/schemas/result.schema.json (t3/server at 6a623d8) and answers
are validated against it at runtime; a test fails when the copy, the
generated types and the server's schema disagree.

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

vercel Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
house-scanning-web Ready Ready Preview Sep 26, 2026 10:05am UTC

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the size:XXL 1,000+ effective changed lines. label Sep 26, 2026
- A new answer no longer arrives dimmed: the 150ms delay applies only
  to dimming a stale answer, not to undoing it.
- Screen readers hear one line per state from a status region instead
  of the whole answer; the verdict is a heading and the page has one h1.
- Focus returns to Change when the server form closes; Escape cancels;
  the address field has URL keyboard hints, a 16px font (no iOS zoom)
  and a 3:1 edge.
- The root font size follows the browser setting; no grey tap flash;
  forced-colors and reduced-motion states keep their meaning.
- Answers are validated by a generated standalone validator instead of
  Ajv compiling the schema with Function() at load: no eval, and the
  bundle drops from 125 to 92 KB gzipped.
- State: health follows each answer, a file refused in the browser
  aborts the request in flight and can't be resent, changing the
  server aborts the old request and retries a failed scene, storage
  errors are survivable, and a file pick clears ?sample=.
- Drops outside the zone no longer navigate away; the zone doesn't
  flicker; the site plan reserves its size; replacing an answer only
  re-stamps instead of replaying every section.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Behavior-preserving: the failure view reads its input once instead of
re-checking it inside each handler, and an unnamed policy reads as
"unnamed" rather than "?".

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

Vercel answers an upload over 4.5 MB with a bare 413 before the server
sees it; the page now says the upload is too big and to send scene.json
or a zip without the photos, instead of calling the server unreachable.
The README records that previews build with VITE_PLACEMENT_API set to
https://house-scanning-server.vercel.app.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SamGu-NRX and others added 2 commits September 26, 2026 05:59
#15 is stacked on #11: the vendored result schema is compared with
server/schemas/result.schema.json, which only exists on t3/server until
#11 merges.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Size: the hosted server's host refuses bodies over 4.5 MB without
  CORS headers, so the browser saw only a network error and the page
  said to start uvicorn. The page now refuses such files before sending
  and, for a zip, says scene.json alone is enough.
- One upload per refusal: the answer is requested first; the site plan
  is asked for only after it, and fills in when it arrives.
- HTTP errors without the server's format (a 504 from the host, a 404
  from another site) are reported as HTTP errors with their status, not
  as an unreachable server. An http:// server entered on the https page
  gets a warning, and its failure says the browser blocked it.
- The stamp reads "Needs review" whenever the rules aren't approved for
  automatic decisions, whatever the decision field says.
- Samples are re-recorded under the public rules the hosted server uses
  (all manual review), replacing an invented auto-approving policy.
- Contract: answers with added fields are accepted within the same
  major schema version, as the iOS app does. The drift test against
  server/schemas now always runs (this branch is stacked on t3/server),
  and web CI also runs when server/schemas changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX
SamGu-NRX changed the base branch from main to t3/server September 26, 2026 11:05
@vercel

vercel Bot commented Sep 26, 2026

Copy link
Copy Markdown

Deployment failed for project house-scanning-web with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/sam-gus-projects-7a4b6082?upgradeToPro=build-rate-limit

SamGu-NRX and others added 2 commits September 26, 2026 08:08
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The server's requests now say how far a view must reach. The vendored
schema and the generated types and validator are regenerated, and the
samples re-recorded against the same server.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX
SamGu-NRX marked this pull request as ready for review September 26, 2026 13:17
@codemetrics-ai

Copy link
Copy Markdown

Note

Review skipped

Codemetrics is installed on this repository, but it hasn't been added to your team yet.

To enable AI code reviews for this repo, go to your dashboard and add house-scanning to your team's repositories.

@SamGu-NRX
SamGu-NRX marked this pull request as draft September 26, 2026 14:03
SamGu-NRX and others added 2 commits September 26, 2026 10:28
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The hosted server now decides under a labelled demo policy, so the
saved sample answers are re-recorded under it (a pass, a manual review
and a reject, each saying the rules are not Base's), and the vendored
result schema adds policy.notice. The server appends the notice to the
summary, so the page shows it without a change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

Closing without merging. The page has been parked since the morning because the product is the iOS app, and it was useful only for checking the server before the app could upload. The branch stays if a reviewer view comes back.

@SamGu-NRX SamGu-NRX closed this Sep 26, 2026
SamGu-NRX pushed a commit that referenced this pull request Oct 15, 2026
A6 imports lamar_io with a star import, which ruff flags as F403 and F405. Those folders are synced from the store, so the ignore belongs in the breadth per-file-ignores.
@SamGu-NRX
SamGu-NRX deleted the t3/web-review branch October 15, 2026 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ effective changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant