Quality gates / quality (pull_request) Successful in 3m9s
A confirmed log and the image attached to it are separate things the person owns. The journal now reports a saved photo as a label instead of a thumbnail, reveals the image only on explicit request, removes the photo without touching any confirmed field, and deletes a log through a separate two-step decision that takes its photo with it. Delete-all is regression tested to remove the ledger and every stored photo together. - src/photo-lifecycle.js: presence decided by stored image bytes; withoutPhoto/withoutEntry return new lists and never mutate the ledger - app.js: journal rows expose Open-entry controls; entry sheet states presence, offers explicit reveal, remove-photo-keeps-log, and a two-step log delete; privacy copy names photo removal and delete-all - tests: 6 unit cases + browser acceptance (presence, no default thumbnail, explicit reveal, remove keeps log, two-step delete, delete-all leaves no image bytes, Escape dismiss, 44px targets) - record_release_demo.mjs: on-camera photo lifecycle scenes driven by a synthetic 1x1 PNG, with delete-all actually performed and asserted; build_release gates the new captions - CI/release/syntax gates include the new suite Closes #36
27 lines
1.1 KiB
JavaScript
27 lines
1.1 KiB
JavaScript
// Photo lifecycle for saved entries. A confirmed log and the image attached to it are separate
|
|
// things a person owns: presence is reported from the stored image bytes, removing the image never
|
|
// touches the log, and deleting the log is a separate, explicit decision.
|
|
const IMAGE_DATA_URL = /^data:image\/[a-z0-9.+-]+(?:;base64)?,[A-Za-z0-9+/=%.-]+$/i;
|
|
|
|
export function photoState(entry) {
|
|
return IMAGE_DATA_URL.test(String(entry?.photoDataUrl || ''))
|
|
? { hasPhoto: true, label: 'Photo saved' }
|
|
: { hasPhoto: false, label: '' };
|
|
}
|
|
|
|
export function withoutPhoto(entries, id) {
|
|
if (!Array.isArray(entries)) return [];
|
|
if (!entries.some(entry => entry.id === id)) return entries;
|
|
return entries.map(entry => (entry.id === id ? { ...entry, photoDataUrl: '' } : entry));
|
|
}
|
|
|
|
export function withoutEntry(entries, id) {
|
|
if (!Array.isArray(entries)) return [];
|
|
if (!entries.some(entry => entry.id === id)) return entries;
|
|
return entries.filter(entry => entry.id !== id);
|
|
}
|
|
|
|
export function photoCount(entries) {
|
|
return Array.isArray(entries) ? entries.filter(entry => photoState(entry).hasPhoto).length : 0;
|
|
}
|