D52. The plan is written at any time; its versions are made by hand, one is validated, and what changed is computed (16 Oct 2026)
The circuit of D5, D35 and rule A5 was rejected on 16 October: once validated, every edit of the plan was a change request (changes/CR-*.json, a JSON Patch and a reason, opened in a window at each field left or item dragged) that a human applied, which put the plan back in « Brouillon, à valider de nouveau »; rewording a validated text on a screen was something else again, a retouch kept on the block until approve_retouches. A session of work made fifteen requests to apply one by one, the author filled « why » for nobody (the client sees nothing before the next send), the plan oscillated between validated and draft, the validation covered the plan as it stood and not what the client had read, and what the client signed was not kept (only its hash). Editing should be natural and transparent, around diff, commit and milestone: this entry takes that model and the ten recommendations of the app’s mockup of the plan’s versions, with one rule of its own about exporting. It is a clean break before publication, with no migration (a plan with status and approval breaks the plan’s schema).
plan.jsonis always the working copy. The author and their agent write it at any time, before and after a validation:write_plan(renamed fromdraft_plan, since it no longer writes only a draft; CLIelabora plan write, MCPelabora_write_plan), checked against the plan’s schema first, the screens following the items it moves (D50). Rewording a validated text on the canvas writes the plan’s text (set_plan_text,elabora plan text): the block keeps itstextRefand shows the plan’s wording, so a retouch is a plain edit.set_plan_voiceoverwrites.statusandapprovalleaveplan.json: the plan’s state follows from its versions.- A version is a frozen copy, made by hand (
plan-versions/V<n>.json, schemaplan-version): its number, its day, an optional note, its content hash (planContentHash: the plan without$schema,decidedandquestion) and the plan as it was never change; the days it was exported for the client (sends: day and format) and the client’s validation (validation: by, day, reference) are added to it. A version whose plan no longer gives its hash, or whose number is not its file name, isE_PLAN_VERSION_INVALIDand does not count. A person makes one (create_plan_version,elabora plan version [--note], the app’s « Créer la V3 »), refused when nothing changed since the latest or when the plan breaks its schema. - Validating marks the version the client read (
approve_plan,elabora plan approve [--version Vn] --by … [--reference] [--date], the latest version by default), never the working copy: a plan without a version is refused (« make one first »), a version validated twice too. It removes what the agent decided and asked from the working copy (D51). Weighed: validating the working copy as before (it would cover changes the client never read, the risk D35 meant to remove). - Exporting never makes a version. An export should do what one expects: show the project’s current state in another file format, or, if that is clearer, the latest version made, but never make one implicitly; perhaps with a warning (« this export shows version x, though changes were made since ») that offers to make a new version on the way. The opposite (a version made at each send) had been recommended. So
exportPlanDocument,elabora plan export [--version Vn|current]andelabora_export_plan_document { version }show the version asked, the latest by default, or the working copy (current, and by default when there is no version), record the send on the version shown (record_plan_send, one change), and never make one; the engine gives the app what its warning needs (planVersions.export: the version an export shows and how many changes it would not show). « Figer une version » and « Créer la V3 » are one gesture, by its number. - The diff is computed, never written (
@elabora/coreproject/plan-diff.tsplanDiff(base, current, course?)): elements matched by their plan ids (objectives, items, texts and questions wherever they are, sources, the plan’s own fields), each added, removed or changed, moved (from its position, the fewest elements moving: the others kept their order), an item’s part changed (the part it belongs to as D50 counts parts, not the field it is written in: an item’s own, else the one before it, and in a plan without parts the title of its section in the course, which the callers with a project give (planCourse); a plan that writes each item’s part down the first time its author drags one, as the app does, changed nobody’s part, so the drag is one change, the item’s rank; a part given that is not the section’s, or taken away, is a change; from a base without parts and without a course there is no part to compare. What the course called a part before a change that moves screens is not kept anywhere once they follow, so in such a plan an item dragged to another part, or a part renamed, shows only through the rank when it moved, until the next version holds the parts), each field changed, texts word by word (diffRich),verbatimfor a text the base kept « à l’identique ». Every change has a key (item:ITEM-3). It gives both references the app’s views use (planVersionsView): the changes since the latest validated version (the marks of Sommaire and Cartes, the document’s tracked changes) and those in no version yet (the pill’s count).revertPlanChangeandrevert_plan_changebring one change back as a version has it (« Revenir à la V2 »), its screens following.elabora plan diff [--from] [--to], MCPelabora_plan_diff(read-only), the engine’splanDiff. - A note for the client per change (
set_plan_note,elabora plan note), optional, waits inplan-versions/notes.jsonuntil the next version freezes those whose change it holds (the others, on changes undone since, are dropped and said). The document shows the notes of the versions since the validated one in its margin and Word as comments (author « Note pour le client »). Weighed: inplan.jsonoutside the hash, as D51’s decisions (an agent writing the plan whole would erase them). - A text kept « à l’identique » (
lockedTexts[].verbatim, the app’s word for it) is changed by a person only, deliberately (the app’s « Déverrouiller » in place): an agent’s write that changes its words, removes its flag or removes it is refused (E_PLAN_VERBATIM_LOCKED, words that tell it to ask the author); one changed since the validated version is flagged (W_PLAN_VERBATIM_CHANGED, amber in the app and in the document). Weighed: untouchable, to remove and create again (a ceremony for a typo in a legal notice). - Agents write the plan, never versions. Making a version and recording a validation are refused to an agent author (
E_PLAN_PERSON_ONLY: the CLI run by Claude Code or Codex, a non-interactive caller, the engine’s agent author), and no MCP tool does them; commands know who runs them (CommandContext.author). An agent’s change after a validation is written directly: the app traces it for the author with « Annuler », and the diff shows it to the client at the next send (weighed: suggestions to accept one by one, as Google Docs does). - Rules.
E_PLAN_NOT_APPROVEDis « no validated version », for release builds; draft builds sayW_PLAN_DRAFTas before.E_PLAN_MODIFIED_AFTER_APPROVALbecomes the warningW_PLAN_MODIFIED_SINCE_VALIDATION: delivering changes the client has not validated is allowed and said, in the build report (plan.validated,plan.changesSince) and the proof dossier (weighed: refusing until a new version is validated). TheE_RETOUCH_*codes andI_TEXT_RETOUCHEDgo with retouches,W_CHANGE_REQUEST_INVALIDwith change requests. - The document (D48) shows a version or the working copy (format
elabora/plan-document/2): its head says its status (« Bon pour production » for a validated version, « Modifié depuis la V2 validée », « À valider », « Brouillon »), the version shown (« V3 », « V2, et 4 modifications depuis »), who validated the version its changes start from, and the history up to it (« V1 envoyée le 1 octobre 2026 », « V2 validée par … le … »). Its changes since the latest validated version up to the one shown read in place, in HTML as marks and in Word as tracked changes; their author is « Depuis la V2 validée », never a person (the app’s mockup signed them with the author’s name: the rule of D48, never a writer, wins), with no date (a change since a version has no day of its own). It still never names an AI or any writer. - Where they go. Versions and notes are files of the course: its history, its backups and its
.elaboraarchives carry them; no SCORM or web package does (tested). A translated course holds its approved translation as its own validatedV1and none of the source’s versions.elabora renamerewrites the plan’s waivers that name the id. - Gone:
changes/CR-*.jsonand thechange-requestschema,propose_plan_change,apply_change_request,reject_change_request,elabora change apply|reject,elabora_propose_plan_change; the blocks’retouch,retouch_text,restore_validated_text,approve_retouchesand their commands and tools;draft_plan,elabora plan draft,elabora_draft_plan;planApprovalState(plan)(nowplanState(project)),planEverApproved,planFrozen,validatedOnce. Lost, as foreseen: the reason required for each change (now an optional note), and an AI’s proposal as a separate object (now its trace with « Annuler » and « Revenir à la V2 »). - The course
AGENTS.md(rule 1, rule 3, the layout and the workflow’s first step: revision 4), the authoring, pedagogy and free-objects skills, and the MCP server’s instructions say it.
Revisit if: clients validate from the review link themselves (then validation carries a verified name and the app no longer types it for them), authors want to unvalidate a version recorded by mistake, a second approver for one side is needed (validation.cosigners), or agencies ask for a version made at each send after all.