T7.7 - CR-007/D8: the sheet travels with the package, and opens offline

The field wants the specific PDF attached, not a link to a Bluebeam session.

Storage: a new wp_files table (Alembic f3a9d2c1e8b7, additive only) holding
the BYTES in the same database as everything else - the Aug 18 decision: a
backup that excludes the drawings is a backup you cannot restore from. The
D8 numbers bound the cost and are enforced ON THE SERVER as well as in the
browser: 5MB a file (413, naming the limit), PDF and image mimes only (400,
naming what is accepted), 2GB a project (413 naming the ceiling; response
flags the 80% warning). The ceiling is env-overridable for tests; the shipped
default is the decision, asserted from source.

The package record carries a server-owned meta mirror (data.files): the
upload/patch/delete routes rewrite it, and the upsert re-asserts the stored
copy over whatever a client sends - a save from a browser that had not seen
an upload land cannot erase the list.

Creator: uploads live beside the links (links still work), the limits and the
running project total sit ABOVE the picker (amber from 80%, red at full), a
refused file costs nothing but a toast and never leaves the browser (the
probe counts fetch calls), and each drawing has a description ("Tray section,
Level 3 east only") editable inline and persisted server-side. Uploads attach
to the saved record, so T4.3's autosave keeps the surrounding form safe (X8).

Export: uploads print with the package - name, size tag, description on the
attachments table, images inline as the sheet itself, PDFs as links.

Offline (D8): the service worker gains a drawings cache (cache-first on
/api/files/), and field.js prefetches ONLY the requesting user's assigned
packages - assignment-scoped by decision, not project-wide. The probe's first
offline check used CDP network emulation and PASSED FOR THE WRONG REASON: the
emulation binds to the page's session and the service worker fetches on its
own target, straight past it. The shipped check kills the server instead -
my drawing opens, the other package's does not, against a genuinely dead
network.

Field View: a Drawings section on the package detail, 44px rows, description
inline, inside the 390px screen.

Verification (each probe run alone): NEW tests/files_check.py 36/36; the
Alembic chain applied end-to-end to a scratch DB and the table verified.
Regressions: form_structure_check 50/51 (the standing F6 height gap),
frame_check 39/39.

Items: CR-007, D8 (X8 honored)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 11:09:35 -07:00
parent 2486f87010
commit c084f730b3
11 changed files with 774 additions and 7 deletions

View File

@@ -581,6 +581,20 @@
.pill-hold.selected { background:var(--red) !important; border-color:var(--red) !important; }
.pill-hold.selected .dot { background:var(--cds-text-on-color) !important; }
/* CR-007/D8: the upload strip in Drawings & Attachments. */
.file-rules { margin:10px 0 6px; font-size:12px; color:var(--text-muted); }
.file-rules .fr-warn { color:var(--accent-amber); font-weight:700; }
.file-rules .fr-full { color:var(--red); font-weight:700; }
.wp-file-row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin:6px 0; }
.wp-file-row input[type="text"] { flex:1 1 240px; }
.wp-file-list { display:flex; flex-direction:column; gap:6px; margin:6px 0; }
.wp-file-item { display:flex; gap:10px; align-items:center; flex-wrap:wrap;
border:1px solid var(--border); border-radius:var(--radius); padding:8px 10px; }
.wp-file-item a { color:var(--accent); text-decoration:none; font-weight:600; overflow-wrap:anywhere; }
.wp-file-item .wf-size { color:var(--text-muted); font-size:11px; }
.wp-file-item input { flex:1 1 200px; font-size:12px; }
.wp-file-x { margin-left:auto; }
.cstatus { display:inline-flex; border:1px solid var(--border-strong); border-radius:5px; overflow:hidden; }
.cstatus button { border:none; background:var(--surface); color:var(--text-muted); font-family:var(--sans); font-size:11px;
font-weight:600; padding:4px 10px; cursor:pointer; border-right:1px solid var(--border); }