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:
@@ -65,9 +65,23 @@ function loadWPs() {
|
||||
ProjectData.pullProject(PID).then(function () {
|
||||
WPS = activePkgs(readCache());
|
||||
if (!curId) renderList(); else renderDetail();
|
||||
prefetchMyDrawings();
|
||||
}).catch(function () {});
|
||||
}
|
||||
}
|
||||
|
||||
// CR-007/D8: warm the drawing cache for MY packages while there is a network.
|
||||
// Deliberately only the requesting user's assignments - the decision was that
|
||||
// offline coverage follows assignment, not the whole project's 2GB.
|
||||
function prefetchMyDrawings() {
|
||||
var myId = (window.WP_USER || {}).id;
|
||||
if (!myId || !('serviceWorker' in navigator)) return;
|
||||
WPS.filter(function (p) { return p.assigneeId === myId; }).forEach(function (p) {
|
||||
((p.files) || []).forEach(function (f) {
|
||||
if (f && f.id) fetch('/api/files/' + f.id).catch(function () {});
|
||||
});
|
||||
});
|
||||
}
|
||||
function showNoProject() {
|
||||
var s = document.getElementById('screen-list');
|
||||
if (s) s.innerHTML = '<div class="fld-empty">No project selected.<br><a href="index.html">Pick a project on the home page</a>, then reopen the field view.</div>';
|
||||
@@ -132,6 +146,11 @@ function renderDetail() {
|
||||
'<div class="fld-sub">' + esc(p.subject || '') + (p.type ? ' · ' + esc(p.type) : '') + '</div>' +
|
||||
'<div class="fld-sec"><h3>Status</h3><div class="st-grid">' + stBtns + '</div></div>' +
|
||||
'<div class="fld-sec"><h3>Constraints — ' + openCount(p) + ' open</h3>' + cxRows + '</div>' +
|
||||
(((p.files) || []).length ? '<div class="fld-sec"><h3>Drawings</h3>' +
|
||||
p.files.map(function (f) {
|
||||
return '<a class="fld-drawing" href="/api/files/' + esc(f.id) + '" target="_blank" rel="noopener">' +
|
||||
'📄 ' + esc(f.name || 'drawing') + (f.description ? ' — ' + esc(f.description) : '') + '</a>';
|
||||
}).join('') + '</div>' : '') +
|
||||
'<div class="fld-sec"><h3>Add field update</h3>' +
|
||||
'<textarea class="fld-note" id="fld-note" placeholder="What happened on site? (progress, blockers, notes)" oninput="draftNote=this.value">' + esc(draftNote) + '</textarea>' +
|
||||
'<div class="fld-photo-row"><label class="fld-btn">📷 Add photo<input type="file" accept="image/*" capture="environment" style="display:none" onchange="onPhoto(event)"></label>' +
|
||||
|
||||
Reference in New Issue
Block a user