Persist the dates the form always collected and always threw away
The creation form has always offered real date and time pickers, and the write kept only the label derived from them. The user chose a date; the broker never saw it. Five fields went that way, on create and on update alike, because the shape defined nowhere for them to land. `startDate`, `endDate`, `startTime` and `endTime` are now on the Event shape and carried end to end. `date` is untouched -- it is the label the lists display, and nothing derives one from the other. `themes` is deliberately left out: it is a repeated value, so it needs a cardinality decision rather than one more optional string, and nothing consumes it. Proven on the running application, both paths, asserted only after a reconnect because the optimistic overlay shows the values before anything is durable: written 2026-12-05 / 09:15, read back identical; edited to 2027-01-22 / 08:05, read back identical. An event missing the fields renders clean -- the time line simply absent, nothing thrown. The recorded generator drift no longer reproduces. Regenerating from the UNCHANGED shape produced bytes identical to the committed bindings, which is how the additive diff was separated from the generator's own; that separation was the point of running it twice, and it turned a warning into a measurement. The doctrine still carries the old warning and is now wrong about it. Also worth having: the form composes its label from the ISO values joined, not from the French wording the doctrine gives as its example.
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# Doc-debt — app-architecture
|
||||
|
||||
> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file.
|
||||
> One block = one "big change": `why` + `files` + `verify` (leaves to review).
|
||||
|
||||
## Raw markers (consolidate into blocks, then delete)
|
||||
- TOUCHED src/shared/context/FestipodDataContext.tsx @2026-08-17 (session 0b064e8b-1717-421f-a20e-a4318ad217b1)
|
||||
@@ -0,0 +1,9 @@
|
||||
# Doc-debt — data-layer
|
||||
|
||||
> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file.
|
||||
> One block = one "big change": `why` + `files` + `verify` (leaves to review).
|
||||
|
||||
## Raw markers (consolidate into blocks, then delete)
|
||||
- TOUCHED src/shared/shapes/shex/festipodShapes.shex @2026-08-17 (session 0b064e8b-1717-421f-a20e-a4318ad217b1)
|
||||
- TOUCHED src/shared/context/FestipodDataContext.tsx @2026-08-17 (session 0b064e8b-1717-421f-a20e-a4318ad217b1)
|
||||
- TOUCHED src/shared/data/shapeAdapters.ts @2026-08-17 (session 0b064e8b-1717-421f-a20e-a4318ad217b1)
|
||||
@@ -0,0 +1,7 @@
|
||||
# Doc-debt — tech-stack
|
||||
|
||||
> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file.
|
||||
> One block = one "big change": `why` + `files` + `verify` (leaves to review).
|
||||
|
||||
## Raw markers (consolidate into blocks, then delete)
|
||||
- TOUCHED package.json @2026-08-17 (session 0b064e8b-1717-421f-a20e-a4318ad217b1)
|
||||
@@ -1178,6 +1178,12 @@ function useNgData(): FestipodDataContextValue {
|
||||
await openDocumentInbox(eventGraph);
|
||||
const eventId = await writeEntity(eventGraph, ENTITY_TYPE.event, {
|
||||
title: str(event.title), description: str(event.description), date: str(event.date),
|
||||
// `date` is the human LABEL the form composed; these four are the machine
|
||||
// values it collected, written UNALTERED as the form's own ISO forms
|
||||
// (`YYYY-MM-DD`, `HH:MM`). They are stored alongside the label, never
|
||||
// derived from it — the label is display text and cannot be parsed back.
|
||||
startDate: str(event.startDate), endDate: str(event.endDate),
|
||||
startTime: str(event.startTime), endTime: str(event.endTime),
|
||||
location: str(event.location), distance: flt(event.distance),
|
||||
// No host notion: the creator merely SIGNALS a public event and is NOT
|
||||
// obliged to participate, so the count starts at 0 (the owner-materializer
|
||||
@@ -1231,6 +1237,14 @@ function useNgData(): FestipodDataContextValue {
|
||||
if (updates.title !== undefined) persists.push(updateEntityField(graph, id, 'title', str(updates.title)));
|
||||
if (updates.description !== undefined) persists.push(updateEntityField(graph, id, 'description', str(updates.description)));
|
||||
if (updates.date !== undefined) persists.push(updateEntityField(graph, id, 'date', str(updates.date)));
|
||||
// The machine dates/times travel with the label, not instead of it. A field
|
||||
// arriving as `''` (the form's empty date input) CLEARS the triple rather than
|
||||
// leaving a stale value behind — that is `updateEntityField`'s empty handling,
|
||||
// and it is the right reading here: the user emptied the input.
|
||||
if (updates.startDate !== undefined) persists.push(updateEntityField(graph, id, 'startDate', str(updates.startDate)));
|
||||
if (updates.endDate !== undefined) persists.push(updateEntityField(graph, id, 'endDate', str(updates.endDate)));
|
||||
if (updates.startTime !== undefined) persists.push(updateEntityField(graph, id, 'startTime', str(updates.startTime)));
|
||||
if (updates.endTime !== undefined) persists.push(updateEntityField(graph, id, 'endTime', str(updates.endTime)));
|
||||
if (updates.location !== undefined) persists.push(updateEntityField(graph, id, 'location', str(updates.location)));
|
||||
if (updates.distance !== undefined) persists.push(updateEntityField(graph, id, 'distance', flt(updates.distance)));
|
||||
await Promise.all(persists).catch(err => console.error(`${logPrefix} persist event update failed:`, err));
|
||||
|
||||
@@ -37,6 +37,15 @@ export function adaptEvent(s: UnionSubject): FpEventData {
|
||||
title: one(s, 'title'),
|
||||
description: one(s, 'description'),
|
||||
date: one(s, 'date'),
|
||||
// The four machine values, read back exactly as stored (`YYYY-MM-DD`, `HH:MM`).
|
||||
// `undefined` — not `''` — when the triple is absent: the app type declares them
|
||||
// optional and the screens branch on their PRESENCE (`event.startTime && …`), so
|
||||
// an event written before these fields existed reads as an event without them
|
||||
// rather than one whose dates are blank strings.
|
||||
startDate: one(s, 'startDate') || undefined,
|
||||
endDate: one(s, 'endDate') || undefined,
|
||||
startTime: one(s, 'startTime') || undefined,
|
||||
endTime: one(s, 'endTime') || undefined,
|
||||
location: one(s, 'location'),
|
||||
distance: s.props[`${FP}distance`] ? num(s, 'distance') : undefined,
|
||||
participantCount: num(s, 'participantCount'),
|
||||
|
||||
@@ -54,6 +54,50 @@ export const festipodShapesSchema = {
|
||||
iri: "http://festipod.org/date",
|
||||
readablePredicate: "date",
|
||||
},
|
||||
{
|
||||
dataTypes: [
|
||||
{
|
||||
valType: "string",
|
||||
},
|
||||
],
|
||||
maxCardinality: 1,
|
||||
minCardinality: 0,
|
||||
iri: "http://festipod.org/startDate",
|
||||
readablePredicate: "startDate",
|
||||
},
|
||||
{
|
||||
dataTypes: [
|
||||
{
|
||||
valType: "string",
|
||||
},
|
||||
],
|
||||
maxCardinality: 1,
|
||||
minCardinality: 0,
|
||||
iri: "http://festipod.org/endDate",
|
||||
readablePredicate: "endDate",
|
||||
},
|
||||
{
|
||||
dataTypes: [
|
||||
{
|
||||
valType: "string",
|
||||
},
|
||||
],
|
||||
maxCardinality: 1,
|
||||
minCardinality: 0,
|
||||
iri: "http://festipod.org/startTime",
|
||||
readablePredicate: "startTime",
|
||||
},
|
||||
{
|
||||
dataTypes: [
|
||||
{
|
||||
valType: "string",
|
||||
},
|
||||
],
|
||||
maxCardinality: 1,
|
||||
minCardinality: 0,
|
||||
iri: "http://festipod.org/endTime",
|
||||
readablePredicate: "endTime",
|
||||
},
|
||||
{
|
||||
dataTypes: [
|
||||
{
|
||||
|
||||
@@ -40,6 +40,30 @@ export interface Event {
|
||||
* Original IRI: http://festipod.org/date
|
||||
*/
|
||||
date: string;
|
||||
/**
|
||||
* The day the event starts, as the ISO calendar date the form collects (YYYY-MM-DD) — stored unaltered, never derived from fp:date
|
||||
*
|
||||
* Original IRI: http://festipod.org/startDate
|
||||
*/
|
||||
startDate?: string;
|
||||
/**
|
||||
* The day the event ends, as an ISO calendar date (YYYY-MM-DD); absent for a single-day event
|
||||
*
|
||||
* Original IRI: http://festipod.org/endDate
|
||||
*/
|
||||
endDate?: string;
|
||||
/**
|
||||
* The time of day the event starts, as the form collects it (HH:MM)
|
||||
*
|
||||
* Original IRI: http://festipod.org/startTime
|
||||
*/
|
||||
startTime?: string;
|
||||
/**
|
||||
* The time of day the event ends (HH:MM)
|
||||
*
|
||||
* Original IRI: http://festipod.org/endTime
|
||||
*/
|
||||
endTime?: string;
|
||||
/**
|
||||
* The location of the event
|
||||
*
|
||||
|
||||
@@ -10,6 +10,14 @@ fp:Event {
|
||||
// rdfs:comment "A description of the event" ;
|
||||
fp:date xsd:string
|
||||
// rdfs:comment "The display date of the event (e.g. 'Lun. 16 - Ven. 20 fév.')" ;
|
||||
fp:startDate xsd:string ?
|
||||
// rdfs:comment "The day the event starts, as the ISO calendar date the form collects (YYYY-MM-DD) — stored unaltered, never derived from fp:date" ;
|
||||
fp:endDate xsd:string ?
|
||||
// rdfs:comment "The day the event ends, as an ISO calendar date (YYYY-MM-DD); absent for a single-day event" ;
|
||||
fp:startTime xsd:string ?
|
||||
// rdfs:comment "The time of day the event starts, as the form collects it (HH:MM)" ;
|
||||
fp:endTime xsd:string ?
|
||||
// rdfs:comment "The time of day the event ends (HH:MM)" ;
|
||||
fp:location xsd:string
|
||||
// rdfs:comment "The location of the event" ;
|
||||
fp:distance xsd:float ?
|
||||
|
||||
Reference in New Issue
Block a user