Files
timmy-talking-turd/src/draft.js
T
timmy dce668e72b
Quality gates / quality (pull_request) Successful in 1m54s
feat: guarantee manual fallback and in-session draft recovery (#12)
Logging must never depend on AI availability, and an unsaved entry must not
disappear when the user navigates away mid-log.

- add src/vision-readiness.js so the photo flow consumes one readiness contract
- when the worker is offline/disabled/unknown, offer no AI action and no
  analysis-consent prompt, and keep manual Bristol entry one action away
- add src/draft.js and make the draft own the photo so a chosen photo and
  entered context survive a recoverable close or navigation, with a visible
  kept-draft summary and an explicit start over
- drafts stay in session memory only: a photo awaiting consent is never
  persisted, and reload starts clean
- service worker now refuses to cache image bodies in addition to analysis
  requests and no-store responses
- tests: 2 new unit files, 3 service-worker privacy assertions (1 hold-out),
  1 browser acceptance file, release-demo and CI/package assertions
- release demo now visibly replays the vision-offline pass and the retained
  draft with synthetic data and an in-script assertion
2026-10-07 08:12:43 +00:00

40 lines
1.2 KiB
JavaScript

// A draft is what the user has entered but not yet saved.
// It is kept in this browser only, so a recoverable close or navigation never loses it.
export function createDraft() {
return {
bristolType: 4,
typeChosen: false,
color: 'brown',
urgency: 0,
discomfort: 0,
note: '',
symptoms: {},
photoDataUrl: '',
};
}
export function isDraftDirty(draft = {}) {
return Boolean(
draft.typeChosen
|| draft.photoDataUrl
|| String(draft.note || '').trim()
|| Number(draft.urgency) > 0
|| Number(draft.discomfort) > 0
|| (draft.color && draft.color !== 'brown')
|| Object.values(draft.symptoms || {}).some(Boolean),
);
}
export function draftNotice(draft = {}) {
if (!isDraftDirty(draft)) return '';
const parts = [];
if (draft.typeChosen) parts.push(`Type ${draft.bristolType}`);
if (draft.color && draft.color !== 'brown') parts.push(`${draft.color} color`);
if (Number(draft.urgency) > 0) parts.push('urgency');
if (Number(draft.discomfort) > 0) parts.push('discomfort');
if (String(draft.note || '').trim()) parts.push('note');
if (Object.values(draft.symptoms || {}).some(Boolean)) parts.push('symptoms');
if (draft.photoDataUrl) parts.push('photo');
return parts.join(' · ');
}