Skip to content

0.5.0

Choose a tag to compare

@github-actions github-actions released this 13 Aug 17:41
· 35 commits to master since this release
v0.5.0
b4fa8d8

The theme of this release is that a GEDCOM file is mostly opaque identifiers and
undocumented codes, and every feature below removes a reason to leave the line
you are reading.

The graph panel now draws the family, not the file

GEDCOM stores a marriage as a FAM record that both spouses and every child
point at. Drawn literally that put a nameless join record between every pair of
relatives, made a grandparent four hops from a grandchild, and filled the third
column with boxes that answered no question anyone had asked.

  • Families are collapsed. Nodes are people; edges are the relationships
    between them, derived by joining through the FAM records and then discarding
    them. A spouse and a child are now one hop away, not two.

  • Columns are generations, not hop counts. Laid out by hops, a sibling and a
    grandparent shared a column because both are two steps from the focus — two
    generations side by side, saying something false about the family. Ancestors
    now run left of the focus and descendants right, the direction a family tree
    is read in.

  • Couples sit together, joined by a marriage bar, and every child descends
    from one point on it.
    Two parents fanning independently to four children
    makes crossings unavoidable; one line per child from the marriage makes them
    impossible.

  • Sibling groups hang beneath their parents, and siblings run oldest first
    within the group.
    Nothing in the ordering consults the current view, which
    is what stops a column rearranging itself every time the selection moves —
    a reader clicking along a row of relatives was watching the row reshuffle
    under them.

    This replaced a barycentre pass that ordered each column against its
    neighbours. That reads well on any one drawing and produces a different
    drawing every time, because the arrangement is computed from whoever happens
    to be on screen. Ordering by birth alone held still but tangled. Neither trade
    was necessary: hanging each family beneath its parents makes the two columns
    agree by construction rather than by search.

    Measured over 300 neighbourhoods of Linguist's Royal92.ged: 68% are drawn
    with no crossings at all, 87% with two or fewer, and the ninetieth percentile
    is three
    — against 54%, and a ninetieth percentile of fourteen, before any
    of this. Tests hold both the crossing figures and the stability ones.

  • Marriages are shown, labelled with their year, alongside parent and child
    links. Siblings are drawn only where a family records no parents, since
    otherwise they are already two hops apart through one.

  • Boxes carry dates1901–1975, or b. 1930 where only one is known.
    A tree full of people sharing a name is unreadable without them. Where the
    file records none the box shows the name alone: "Individual" under every name
    is a label with no information in it, and a row of them reads as though
    something failed to load.

  • A family is still shown as itself when it is the record under the cursor,
    with its members around it and their roles named. Collapsing is right for
    families travelled through and wrong for the one being looked at.

  • Ancestors / Descendants / Both buttons, because tracing a line back is a
    different task from following it forward, and each is half the graph.

  • Sources, notes and media are left out unless gedcom.graph.includeReferences
    asks for them: a well-sourced person cites dozens, and they crowd out the
    family the panel exists to show.

  • A family is never drawn as a box, not even when it is the record under the
    cursor. It is a join, not a person: no name, no dates, nothing to say that its
    members do not say better. Putting the cursor in one shows that family — the
    couple and their children — and highlights all of them.

  • Edges run left to right whichever way the pointer is written, and labels are
    placed with a backing plate, nudged apart where two would collide. The gutter
    between columns was widened so a label such as Married 1874 fits in it, and
    labels are drawn after the boxes: painted before them, a marriage year
    ended up half hidden behind the spouse below it.

  • Relationships that cross no generation — siblings, citations, and a couple —
    are routed down the side of their column. Drawn as a left-to-right curve such
    an edge doubled back on itself and dropped its label behind a box.

The details panel

A second view in the same GEDCOM panel, for everything the graph has to
discard in order to stay readable.

  • Selecting a person shows what they actually contain: occupation,
    residence, the place they were born, notes, citations, identifiers — composed
    generically from the registry, so a tag nobody anticipated appears rather than
    being dropped silently. Every field jumps to its line when clicked.
  • With nothing selected it describes the file: the program that wrote it,
    the character set, the counts, and the submitter with their address and notes.
    None of that is a person or a family, and drawn into the graph the submitter
    became a box with no generation and no relationships. An unreferenced SUBM
    is found too — PAF-era files carry one that the header never points at, and
    Linguist's own Royal92.ged is one of them.
  • Clicking a box in the graph now selects rather than navigates. Reading down
    a line of descent means looking at a dozen people in turn, and jumping the
    editor to each one loses the reader's place in the file for no benefit.
    Navigation is its own gesture, on a button that appears on the box.
  • Text written across CONT lines is shown whole — monospace, wrapped, in a
    block of its own — whether the record is reached as the file's submitter or
    selected in its own right. Royal92.ged records the file's provenance as a
    twenty-eight line posting from 1992, and only its first line was reaching the
    panel; its three-line address fared no better. The parser's reassembly was
    never at fault, and a test now proves it: this layer was discarding the rest.
  • A short list of vendor tags the registry has never covered are named in
    English — COMM is PAF's comment field, and it is what carries that
    provenance note. The list stays short deliberately: a confidently wrong label
    is worse than a bare tag.
  • A record that is neither a person nor a family no longer appears in the graph
    as a lone box with no generation and no relationships. The panel says where to
    find its contents instead.
  • The graph scrolls the selection into view when the neighbourhood is larger
    than the panel, on whichever axis actually overflows.
  • Children descend from the parent whose line is being traced rather than from
    the midpoint between the couple. The midpoint is nobody — it floats in the gap
    between two boxes, and a reader following a descent cannot tell which of the
    two it belongs to.

Added

  • Inlay hints, off the back of that: what each pointer names
    (1 FAMS @F1@John Smith + Jane Doe), what each coded value means
    (1 SEX Mmale, 2 QUAY 3primary), a language tag's language, and how
    old the subject was at an event, computed against their own BIRT date. Each
    kind has its own setting, because the resolved names are indispensable in an
    unfamiliar file and noise in your own. An age carries the verb of its event —
    died age 73, married age 24 — and every hint is set apart from the payload
    so it does not read as though the file itself said 1 SEX M male.

  • Code lens above each record: its shape in the tree, a clickable reference
    count that peeks every pointer to it, and a link into the graph panel. Above
    HEAD, the dataset summary the header does not carry — record counts and the
    span of years the file covers.

  • Document links on WWW, EMAIL and URL-valued FILE payloads.

  • Per-verb hovers. The rule they are written against is that a hover must
    answer the question the line provokes rather than restate the tag:

    • DATE — the weekday for an exact date, what a qualified date is claiming,
      and how old the subject was; under CHAN or CREA, how long ago instead,
      because whether a record is maintained is the actual question there.
    • AGE — the notation in words, cross-checked against the recorded dates.
      An age that disagrees with the file's own birth and event dates is flagged.
      Nothing else in the format checks the two against each other.
    • PLAC — the jurisdictions labelled from HEAD.PLAC.FORM, which almost no
      tool surfaces, plus coordinates from a MAP and a link to a map service.
    • NAME — split into given name, surname and suffix on the slashes, with a
      note when no surname is marked.
    • NCHI — how many children are claimed against how many are recorded, which
      is the research question rather than a restatement.
    • Enumerated payloads across QUAY, PEDI, RESN, SEX, FAMC.STAT,
      NAME.TYPE, MEDI, ROLE, FAMC.ADOP and the LDS ordinance statuses. When
      a value is outside its set, the alternatives are listed.
    • LANG resolved through BCP 47, FORM through media types in both the modern
      and 5.5.1 spellings, and the identifier families (REFN, UID, EXID,
      RIN, AFN, RFN) distinguished from each other.
    • A migration note on any tag the file's version removed, carrying the
      replacement — ROMN says to use TRAN.
  • Cardinality stated in words: "Required, exactly one" rather than {1:1}.

  • The detected version, in the status bar. It governs how every line in the
    file is read and is frequently guessed rather than declared, so a reader who
    disagrees with the guess needs to see it before anything else makes sense. An
    inferred version is flagged with the warning background; the tooltip explains
    how it was arrived at and what the file contains.

  • Payload shapes checked in the grammar, so a wrong value is coloured as
    wrong while it is being typed rather than only appearing as a squiggle:

    • a pointer-only tag given anything but a pointer (1 ASSO @I1@ df),
    • SEX, QUAY, PEDI and RESN given a value outside their set,
    • NCHI, NMR and the other counts given something that is not a number,
    • AGE and TIME given something that does not fit their notation.

    Each rule covers only tags whose payload shape is fixed in every context and
    in both generations, and extension values are always let through. SOUR is a
    pointer under INDI and free text under HEAD, so it is not checked; MEDI
    is not, because 5.5.1 wrote its values in lower case and 7.0 in upper. A scan
    of the whole fixture corpus finds no line these rules fire on.

  • English names everywhere a tag was shown raw: hover titles lead with the
    name and keep the tag alongside, pointer targets read "Points at an
    Individual record", completion details carry the name, and misplacement
    diagnostics name both structures. Twelve 5.5.1-era tags the registry labels
    nowhere are named here.

Fixed

  • A pointer payload with anything after it was silently accepted.
    1 ASSO @I1@ df parsed as text, was never indexed as a reference, and drew no
    diagnostic — the payload simply stopped being a pointer and nothing said so.
    Every structure the registry types as a pointer now requires its payload to be
    exactly @xref@, at every strictness, because it is wrong under any reading of
    any version.

  • Vocabulary diagnostics never said what they were judging against. "Not a
    tag in this version of GEDCOM" asked the reader to take on trust both which
    version that was and how it had been decided — and when the answer is "we
    guessed from the tags in use", that is exactly what they need to know, because
    the right fix may be to correct the header rather than the line. Every such
    message now names the version, says whether it was declared, inferred or
    defaulted, and links to the specification.

  • Hovers on CHAN copied the date from two lines below instead of qualifying it.
    Events read as sentences now, and maintenance dates say how long ago.

  • The extension could not load in the web extension host at all — the same
    class of failure as 0.4.0's, in the other host, and found the same way: by
    writing the test that could see it. The worker host decides a module's kind
    with

    path.endsWith('.mjs') || (extension.type === 'module' && !path.endsWith('.cjs'));

    and then refuses ESM outright, because it supports none. This repository is
    "type": "module", so dist/browser/extension.js was read as ESM and threw
    before activation. The browser entry is now .cjs, matching the Node one. The
    server bundle stays .js: it is passed to new Worker() by URL, and a worker
    script must be served with a JavaScript MIME type.

  • Hovers showed the registry's internal type URIs. A structure taking plain
    text was described as XMLSchema#string and a date as type-DATE_VALUE. Every
    payload type is now given as a description with an example where one helps, and
    a test iterates the whole registry so a newly-added type cannot slip through
    undescribed.

  • relativeTime counted two calendar years spanning a leap year as one, because
    730 days divided by a mean year length floors to 1. It now counts calendar
    years and months.

  • The age on an event hover named the event by its tag rather than its label,
    reading CENS at 9 years old. The label is keyed by the registry slug, and the
    slug for CENS inside an INDI is INDI-CENS.

Testing

  • Integration tests in the web extension host, headless via
    @vscode/test-web (vp run test:web), covering activation in the worker, the
    language server answering from a nested worker, and the graph panel resolving
    under the browser content security policy. This closes the last host that
    shipped without automated coverage — and found the activation bug above on its
    first run.
  • Tests for every new module, including one that walks the whole registry and
    fails if any payload type reaches a reader undescribed, and one that scans the
    entire fixture corpus for a line the new grammar rules wrongly reject.
  • The web harness pins --quality=stable. @vscode/test-web otherwise fetches
    whichever Insiders build is newest that day, so the same commit passes or fails
    depending on when it runs — and a broken Insiders build hangs before invoking
    any test module, printing nothing at all. Each web test is also raced against
    a clock, because a runner that hangs silently is the worst failure mode there
    is: it looks exactly like an environment problem and names no line.

Changed

  • Dependencies. Every package is at its latest published version, with two
    deliberate exceptions, both now enforced by tests rather than left to memory:
    • @types/vscode stays at ~1.91.0, matching engines.vscode. It decides
      which APIs the compiler believes exist, so raising it alone would compile
      calls to APIs missing from the oldest editor the extension claims to run on
      — a failure that lands on a user at runtime. The published types are twenty
      releases ahead; nothing here needs them.
    • serialize-javascript and diff are pinned through overrides to versions
      the advisories are fixed in. Both reach the repository only through
      @vscode/test-climocha, neither ships in the extension, and neither
      could be fixed by an upgrade — which is why the security job failed on every
      run. npm audit is now clean. Both replacements were checked against the
      code that uses them, including mocha's failure-diff rendering, which a green
      suite never exercises.
  • devEngines.packageManager takes a range (>=11) and warns rather than
    failing. It pinned npm to one exact patch version, which no CI runner happens
    to have, so npm ci aborted with EBADDEVENGINES before doing anything. The
    real requirement was always "a modern npm".
  • vp run build is the build task. It was bundle, which nobody guesses.
  • F5 now runs it first. The extension host loads the bundles in
    dist/, so launching without a rebuild looked exactly like a code change
    having had no effect.