Atelier / Archive Duchastel · Fiches Duchastel → · Français

Archive Duchastel

Turning a four-page blog into a bilingual, cross-referenced archive of people, places, and trades — one that shows its own uncertainty and invites the family to help resolve it. Eight stages, none discarding the last.

Stack  Hugo 0.165 · Cloudflare Pages · static Scale  100–1,000 persons Author  Single maintainer, git Languages  Français · English Status  Stages 0–2 complete · 8 stages
I

What we settled

Source of truth
This repository A native format, designed here and entered by hand. A transcoder from the external system can come much later, or never.
Object types
An open registry Types and relationships are themselves data. New kinds of thing get added without writing code.
Scale
100–1,000 persons Enough to need generated pages and real directories; still a fast static build.
Authoring
Solo, via git No CMS, no database, no server. Markdown and YAML, versioned.
Languages
Tiers 1 + 2 Interface and summaries bilingual; deep archival narrative French with English glosses.
URLs
/fr/ and /en/ Symmetric. Neither language is second-class, and a third slots in without restructuring.
Confidence
Shape and word No colour. Every mark stays legible in greyscale, in print, and to colour-blind readers.
Untranslated pages
French + notice Content is never hidden from an English reader — it is shown with a banner, and queued.
Privacy
Birth dates only Century rule, or ten years past death. Suppressed dates reduce to a decade; deaths and marriages stay public.
II

Where the site stands today

1,721words on the entire site
95%of them on one page
3stub families of nine words each
0person or place objects

This is close to greenfield, which is the most useful thing about it. Nothing below is a migration problem — it is a set of decisions that were never made because a blog theme made them for us.

III

The destination

A static Hugo site with a hybrid core: structured YAML carries the skeleton — people, dates, coordinates, relationships — while markdown carries the narrative. Hugo joins them at build time. Facts stay consistent and machine-checkable; prose stays pleasant to write.

An open registry, not a fixed set of types

Hardcoding a fixed set of types would be a mistake, and avoiding it costs almost nothing when done from the start. So types are not code — types are data. A type descriptor declares its fields, which sections it supports, and how it is grouped in directories. A generic template reads the descriptor and renders. Adding a kind of thing means adding a file, not writing a layout.

Registry — data/schema/, declared once
person place lineage activity source + organisation + artefact
union parentage tenure residence + apprenticeship

Relationships are registered the same way types are: each declares its endpoints, its cardinality, its inverse, and its label in both languages.

Records — data/, what gets typed
persons/duchastel-louis-francois-1784 places/monthoury unions/duchastel-legoix-1813
Pages — generated, never hand-maintained
object pages directories trees maps research log translation queue

Register a new type in the top band and its pages, directory, and search entries appear here — with no template written.

Why “family” did not survive as a type

Family is not a safe type. It conflates three different things: a surname lineage, a conjugal unit, and a household at a moment in time. The family records prove the problem — du Chastel, Duchastel, Duchatel, Dûchatel, and Duchastel de Montrouge are one lineage with five spellings, and people marry in and out of it constantly.

The replacement

A lineage is a facet for grouping and search, not a membership. It still gets a page, and that page still lists key members — but that list is curated by hand, not computed. A family page is a story someone chooses to tell, not a set the machine derives.

The conjugal unit becomes a union record, and descent becomes a parentage record. Both are first-class objects rather than fields on a person.

Relationships are records, which is what makes the hard cases easy

Moving unions and parentage off the person and into their own records is the single change that makes the difficult cases easy. Divorce, second and third marriages, unknown parents that become known — none of them are special cases in the code. They are field values.

CaseHow it is recorded
Marriage ended by deathend.reason: death
Divorceend.reason: divorce
Second, third marriageAnother union record, order: 2, order: 3
Children without marriagekind: liaison
Spouse known to exist but unnamedpartners: [x, null]
Parents unknownNo parentage record — nothing to contradict later
Parents presumed, then provenOne parentage record; raise its conf
Two candidate fathersTwo parentage records, each with its own confidence
Adoption, step-parentagekind: adoptive, kind: step

Every one of these is worked through on real records from the family notes in Fiches Duchastel — including the second marriage to an unnamed wife, the parentage the archivist could only presume, and the construction date two sources disagree about.

A component library, not one-off pages

Building generic, reusable pieces rather than one timeline at a time is what makes the later stages cheap. Seven components cover every requirement.

timelineChronology

Any array of dated entries, rendered vertically inline or compactly in a popup. Written once, driven by data.

any object type
figureImage and media

Hugo generates responsive thumbnails; clicking opens an overlay with caption, credit, and a download button for the full-resolution original.

every page
treeFamily tree, two modes

Focus draws 2–4 generations around one person. Sparse draws many generations with few nodes, to expose links between lineages.

person · lineage
mapLocated places

Keyed markers with a legend beneath, and hover tooltips carrying their own text. Scales from a city quarter to a continent.

place · lineage · global
refsCross-references

Links to related objects, resolved from IDs so a renamed page can never leave a dead link behind.

every page
questions · notesOpen threads

Two collapsible blocks for what is unresolved and what is worth recording. Optional everywhere, and collected into the research log.

every page
directoryGrouped index

Generated listing over any registered type, grouped by lineage, period, region, or trade. New objects appear without touching a template.

any type

Dates are data, never prose

The decision most likely to cause pain if deferred. A date written as prose — « vers 1757 » — is frozen in French forever, and invisible to the privacy rule.

StoredRenders (fr)Renders (en)Why it matters
{year: 1757, qual: circa}vers 1757c. 1757Translates itself
{year: 1802, qual: before}avant 1802before 1802Sorts correctly on a timeline
{y: 1813, m: 5, d: 19}19 mai 181319 May 1813Formats per locale
{year: 1955}— suppressed by privacy rule —Checked mechanically, everywhere

The privacy rule, settled

Only birth dates are protected. This matches how the records themselves are governed: birth records are typically restricted for 75–100 years, while death and marriage records are restricted for only 50–75. And the century threshold is the genealogical standard — British genealogists publish under a “GEN100” mark to signal exactly this cut-off, on the working assumption that anyone born within the past hundred years is living unless known otherwise.

the whole rule
showBirthDate(person, today):

  # explicit overrides always win
  if person.privacy == public   -> FULL
  if person.privacy == private  -> HIDDEN

  # 1. the century rule
  if today.year - birth.year >= 100          -> FULL

  # 2. the decade-after-death rule
  if death.year and today.year - death.year >= 10  -> FULL

  # otherwise reduce, never erase
  if birth.year -> DECADE      # « née dans les années 1940 »
  else          -> HIDDEN

always public:  death dates, marriage dates, places, names
always suppressed with the birth date:  age at death, age at
  marriage, age recorded in any census

The leak a decade rule leaves open

A decade is safe on its own. It stops being safe next to a death date and an age. Show “born in the 1940s”, “died 2012”, and “died aged 67” together, and 1945 has been republished exactly — any two of those three recover the third.

So suppression has to cascade to computed values, not just to the stored date. That is the real reason this lives in one function: age-at-death is calculated in a dozen places, and each one has to ask permission.

A worked case

Michèle — born 1945, died 2012. In 2020: 75 years since birth (under 100) and 8 years since death (under 10), so the birth reduces to années 1940 while the death date shows in full. In 2026: 14 years since death clears the second rule, so 1945 publishes. That is the intended behaviour.

Worth knowing that the dominant software goes further: webtrees hides everything about a living person and prints “Private” as the name. That suits a site open to strangers. For a family archive, showing the name and reducing the date to a decade is the better trade — a tree is unreadable when it is impossible to tell which generation someone belongs to.

How the two languages divide

The split runs through pages, not between them. A single place page carries all three tiers at once — here is the Montflambert material, sorted:

ContentTierTreatment
Interface, headings, père/father, dates1Both, automatically — a fixed table of ~80 strings
“Situé à environ 29 km au sud de Reims”2Both — original prose, and it is short
“1753 (14 avril) — vendu à Jean-Baptiste du Chatel”1Stored as data; renders in either language
The 1751 marriage-contract extract3French verbatim, English gloss above it
“Maintenant un Bed and Breakfast”2Both — practical information for a visiting relative
Jean-Baptiste · Cormontreuil · ReimsNever translated

Translating a notarial act would destroy its value as evidence — a reader who wants to verify a claim needs the words actually in the book. The gloss carries the meaning; the quote carries the proof.

IV

The format

Because everything is entered by hand and nothing needs to import, the format can be designed for the person typing it. One principle governs the whole thing:

Facts are language-neutral. Prose is per-language.

A birth year is the same number in French and English. Only the writing differs. So facts live once, in YAML shared by both languages, and each language gets a thin markdown file holding only its narrative. Nothing factual is stored twice, so nothing factual can drift.

layout
data/schema/
  types/person.yaml          # the registry: what a person IS
  types/place.yaml
  relations/union.yaml       # endpoints, cardinality, inverse, labels
  relations/parentage.yaml

data/
  persons/duchastel-louis-francois-1784.yaml
  places/monthoury.yaml
  unions/duchastel-legoix-1813.yaml
  parentage/legoix-sophie-1790.yaml
  sources/ad-aisne-4q1e2856.yaml

content/fr/  personnes/…  lieux/…      # French narrative only
content/en/  persons/…    places/…     # optional English
data/schema/relations/union.yaml — a relationship, declared
id:        union
endpoints: [person, person]
ordered:   false              # partners are symmetric
multiple:  true               # a person may have several
label:     { fr: "union", en: "union" }
fields:    [kind, order, begin, end, conf, sources]

Adding apprenticeship as a person-to-person relation, or artefact as a new type, means writing one file like the above. Nothing in the templates changes.

Three conventions worth fixing now

V

Confidence, hypotheses, and asking for help

This is standard practice. GEDCOM has carried a QUAY tag for this since the 1990s — “an assessment of the certainty of the evidence.” Gramps implements it too, and puts the field on the citation rather than on the person, which turns out to be the important part.

The correction: source count is a poor proxy for confidence

Three websites copying the same parish register are one source, not three. A single original register entry — written by a participant, at the time — outranks all of them. Evidence Explained splits this into three axes: original or derivative, primary or secondary, direct or indirect or negative.

Three axes is too much to fill in by hand a thousand times. So one field is kept — but it records a judgement of the evidence, not a count of rows.

One field, five values, two visual families

A hypothesis is not “low confidence” — it is a different kind of claim. Three values form an ordinal ramp; two are categorical flags drawn deliberately unlike the ramp.

ValueMarkMeansTypical evidence
proven Attesté Documented and consistent; defensible Original record, primary information
probable Probable One good source, nothing contradicting it A single register, a reliable published work
possible Possible Weak or indirect; plausible but unconfirmed Derivative source, family recollection
hypothesis Hypothèse A proposal, not yet evidence at all Reasoning from names, places, or gaps
disputed Contradiction Sources genuinely conflict, and it is unresolved Two records giving different years

Colour is deliberately not used

The ramp reads by fill (● ◐ ○); the two flags take an outline instead, so they read as a different kind of mark rather than a further step down the same scale. Each carries a word as well as a shape.

This survives greyscale printing, photocopying, every form of colour vision deficiency, and a phone in sunlight — which matters more than usual here, since a genealogy audience skews older and roughly one man in twelve has difficulty with red and green. If colour is ever wanted, it layers on as a redundant second signal without touching any data.

It attaches to assertions, not to people

Louis-François’s marriage date can be proven while his occupation is possible and his parentage a hypothesis — all on one person, each displayed differently, each collected separately.

What actually makes relatives answer

WikiTree is the instructive case: it enforces at least one source per profile, flags unsourced profiles openly, runs an annual “Source-a-Thon” where progress is visible, and routes every doubt into a genealogist-to-genealogist forum. FamilySearch recommends the same core move. Distilled for a family site with no accounts:

VI

The workbench

Three maintenance pages, all generated from records already written, none hand-maintained. They are the same machinery pointed at three different questions — which is why the third one costs almost nothing once the first exists.

/fr/enquetes/Research log

Every assertion marked hypothesis, disputed, or possible, plus every questions: entry. Grouped by lineage, place, and period; each with its own anchor, so a cousin can be sent one question rather than a page of them.

what we do not know
/fr/traductions/Translation queue

Every object whose French page exists without an English counterpart, and every tier-2 summary still missing one. Sorted so the highest-traffic pages surface first, with a word count, so a job can be picked to fit the time available.

what is not yet bilingual
build reportValidation

Broken references, relations whose endpoints do not match the registry, media files that do not exist, unparseable dates, people with no sources at all. Fails the build rather than publishing the error.

what is malformed

Why the translation queue is free

A missing translation is structurally identical to an open question: a gap the site can detect by itself. Hugo already knows which language versions of a page exist, so the queue is a list derived from pages, not a to-do list anyone maintains. It cannot go stale, and it cannot forget anything.

It also gives the tiers teeth. A missing tier-2 summary is flagged as blocking — an English reader cannot understand the subject without it. A missing tier-3 narrative is flagged optional, because the French plus an English gloss was always the intended end state.

VII

Eight stages

Ordered by dependency, not by appeal. Each stage leaves the site working and deployed; none requires discarding the previous one. The numbering is real — trees need people, people need the registry, the registry needs the foundations.

Where things stand Stage 0 was completed on 21 August 2026 — these pages are its output, published at /atelier/, unlisted and outside the sitemap. Stage 1 followed on 31 August 2026: PaperMod is gone, the theme is ours, and the site is bilingual on symmetric /fr/ and /en/ URLs. Stage 2 landed on 1 September 2026 — types and relations are declared as data, the four houses have their own pages, and the build refuses to publish a cross-reference that does not resolve. Stage 3, the common sections and the workbench, is next.

STAGE0

The atelierComplete · 21 Aug 2026

Publish these planning pages on the site itself, under an unlisted path (/atelier/), linked to each other but from nothing else. Excluded from the sitemap, marked noindex, and deliberately not named in robots.txt — which would advertise the very path meant to stay quiet. Ships first because it costs an afternoon and makes every later stage reviewable by the family.

  • /atelier/ section
  • noindex
  • sitemap exclusion
  • cross-links
  • plan
  • fiches

ResultOne link to send the family, and a habit of writing decisions down where they can be argued with.

STAGE1

FoundationsComplete · 31 Aug 2026

Nothing new appears, and everything afterwards depends on it. Retire PaperMod for a custom theme built around objects rather than posts. Stand up /fr/ and /en/ routing with the interface string table. Convert images to page bundles so Hugo can process them. Build the media component — which finally fixes the lightbox. Write the date model and the privacy function. Upgrade Hugo from 0.147.7 to 0.165.0 in the same pass — PaperMod is the only thing that has ever broken on a version bump, and it is being deleted here anyway.

  • custom theme
  • bilingual shell
  • page bundles
  • figure component
  • date model
  • privacy function
  • Hugo 0.165

ResultThe same four pages, but bilingual, with a working image viewer and dates that know what they are.

STAGE2

The registryComplete · 1 Sep 2026

The spine. Build the type and relation registry, then declare the first types and relations in it. Generate a page per object; resolve cross-references from IDs and build the reverse indexes. The conf field ships here rather than later — retrofitting confidence onto a thousand facts already entered is the one mistake this plan cannot absorb. Prove it on real material by splitting duchastel_maison.md into four places.

  • type registry
  • relation registry
  • generated pages
  • confidence field
  • build validator
  • Cormontreuil
  • Montflambert
  • Crogny
  • Monthoury

ResultOne 44 KB page replaced by four navigable places — and the ability to add a fifth kind of thing without writing a template.

STAGE4

People, unions, lineages

Enter the data. Person records, union records, parentage records — including the awkward ones: remarriages, unnamed spouses, presumed descent. Lineage pages with curated key members. Day and Dion stop being nine-word stubs.

  • person records
  • unions
  • parentage
  • lineage pages
  • media galleries

ResultA real archive rather than a demonstration — the point where the site becomes worth showing to relatives.

STAGE5

Trees

Both modes, on the same underlying records. Focus draws two to four generations around a chosen person. Sparse spans many generations while omitting most individuals, so the links between the Duchastel, Day, and Dion lineages become visible at a glance. Unnamed and living nodes render without leaking anything.

  • focus mode
  • sparse mode
  • zoom & pan
  • privacy-aware labels
  • null-partner nodes

ResultThe relationships legible without reading a single page of prose.

STAGE6

Maps

Leaflet with self-hosted tiles — no API key, no per-view billing, nothing to break when a vendor changes terms. Keyed markers with a legend beneath and hover tooltips carrying their own text, at both scales: the Reims-and-Champagne view, and the broader view showing movement between countries.

  • local scale
  • continental scale
  • keyed legend
  • hover tooltips

ResultEvery place record on a map, and the family's geography as a thing that can be pointed at.

STAGE7

Directories, search, and the English pass

Grouped directories over every registered type. Client-side search. The one-click reply channel on every open question, with a contributor credit line. Then the tier-2 English pass, working down the translation queue — done last, deliberately, because translating content that is still moving wastes the effort twice.

  • directories
  • search index
  • contribution form
  • contributor credits
  • English summaries

ResultThe destination: a bilingual, searchable, cross-referenced archive that a cousin in either language can navigate unaided.

VIII

Still open

Nothing here blocks Stage 1 any more. These are choices worth turning over before they arrive rather than after.

Do photographs of living people follow the same rule?

The date rule is settled and the name rule follows from it — living people keep their names, which is what makes a family tree readable. Photographs are the one case still undecided, and they behave differently: a portrait is not a fact that ages out, and the person in it may simply not want it published.

The proposal is that images default to visible for the deceased and to hidden for the living, with a per-image override — the same privacy: field the person record already carries. That keeps it mechanical rather than a judgement call.

How private does /atelier/ need to be?

Unlisted is not private. Anyone with the URL can read it, and URLs leak — through referrer headers, browser sync, and forwarded email. For planning documents that is almost certainly fine; there is nothing sensitive in them.

If it ever needs to be genuinely closed, Cloudflare Access sits in front of a path and sends a one-time code to approved email addresses. It is free for up to 50 users, needs no accounts for family members, and runs on the existing hosting. Worth knowing it exists before the first page containing living relatives goes up.

Should possible appear in the research log?

Hypotheses and contradictions clearly belong there. Weakly-sourced facts are a judgement call: including them makes the log a complete to-do list, but a long one that may read as discouraging. The proposal is to include them, grouped separately under à confirmer rather than à résoudre.

How visible should confidence be by default?

A mark on every single date makes a page look like a spreadsheet. The alternative is to mark only possible, hypothesis, and disputed — letting an unmarked fact mean “established” — with a toggle that reveals all of them. Quieter pages, same information, one click away. A Stage 3 decision.

IX

Decisions

The choices behind the design, with what each one rules out. Recorded so that anyone joining later can see not only what was decided but what was considered and rejected — and can reopen a decision on the evidence rather than on memory.