Skip to content

v0.5.0 — the product implements a published Markdown specification - #74

Merged
heitorrapcinski merged 18 commits into
mainfrom
release/v0.5.0
Sep 6, 2026
Merged

v0.5.0 — the product implements a published Markdown specification#74
heitorrapcinski merged 18 commits into
mainfrom
release/v0.5.0

Conversation

@heitorrapcinski

Copy link
Copy Markdown
Contributor

The cycle in which the product stopped declaring its own Markdown notation and started implementing a published specification.

Nineteen issues, and the shape of the cycle is best read from what the change to a specification cost and returned: five defects surfaced by a suite this repository did not write, two of them in features that had been shipped and were quietly broken.

Summary of changes

The profile: the product implements a specification instead of declaring one

  • RECOGNISED_NOTATION is now a projection of a published profile.json, not a list beside it, and the version is pinned once in the catalog: of pnpm-workspace.yaml. The Discovery conformance test runs the published cases; a new conformance test in the frontend runs each reading-surface entry through the real renderer, because a rendering assertion cannot live in a JSON file. get_skill opens by naming the profile, its version and the three rings (RN-AGT-022, RN-AGT-023). Closes The product declares its own notation, while the profile it implements now lives outside this repository #61
  • The MCP surface no longer cites business rule codes at the agent. A test walks the whoami text, every tool title, description and schema, every skill and every declared notation, and fails on a rule code in any of them (RN-AGT-021). Closes The MCP surface should not cite business rule codes #52
  • The profile itself was published alongside this cycle, at v0.1.0 and v0.2.0.

The vocabulary, the query and the reading surface

The interface

The defects the profile surfaced

Deferred, with the reason written

#48 (an isolated staging environment) and #57 (the product's e-mail leaving from its own domain) moved to v0.6.0: both wait on a process outside this repository, and a cycle whose closing date is set by somebody else's queue is not a cycle. #55 (editing in the browser) stays undecided, because the shape of writing on the web surface is a decision about the product and not about a screen.

AI productivity analysis

Metric Value
Lines of code handled (added + removed) 5488 (5144 added, 344 removed)
Branch duration 1 day (from 2026-09-06 to 2026-09-06)
Technologies involved TypeScript, React 19, Vite, Vitest, react-markdown, remark, rehype, KaTeX, TanStack Query, React Router, Zustand, i18next, Zod, Node.js, pnpm workspaces and catalogs, DynamoDB, JSON Schema, Markdown, CSS, PowerShell, GitHub CLI

Estimated human effort (without AI assistance)

Estimated effort: 168h, roughly 21.0 working days (8h/day) or 4.2 working weeks (40h/week).

The estimate is dominated by three things rather than by volume. Diagnosis: three of the defects had no visible cause — a task box that fails identically on every note, a session that dies two hours later with no message, a link that renders as pending while the graph holds the edge — and each was found by building the instrument that finds it, not by reading the code. Breadth: the diff crosses a published external specification, two remark plugin layers, a query language parser, a projection schema, eight rendering surfaces and two hand-authored knowledge bases. Judgement: four decisions in this cycle were reversals or refusals that had to be argued and recorded — the alias resolution, the inline-tag conversion, the sign-out asymmetry and the source argument of the task ordinal — and each is the kind that costs a day of thinking and twenty minutes of typing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ

heitorrapcinski and others added 18 commits September 6, 2026 11:02
`kindOf` answered `list` only when an entry held more than one value, so
`tags: [contracts]` was an enum and `tags: [contracts, budget]` was a list.
One attribute therefore had two kinds across the notes of a single vault,
decided by a fact about whichever note was being read, and adding a second
value silently changed what the attribute was.

The frontmatter reader now carries the written form out alongside the
values, and the kind follows the form: a bracketed or dash list is a list at
any length, a scalar is an enum. RN-DSC-020 already said the classification
is by the shape of the value; this is the code catching up with it, so no
rule is created.

The reading surface already drew a list of one as a list, through a
different reader it may not share. It is now pinned to the same cases, so
the two agree by rule rather than by coincidence.

Closes #70

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
Three defects composed into one dead end: the screen sat on "Loading..."
forever, a reload changed nothing, and signing out was the only way through.

The first silent refresh threw the refresh token away. `store()` wrote
`refresh_token ?? null`, and Cognito does not return one on a
`grant_type=refresh_token` exchange, so a missing field was read as a
revoked credential. The access token lives an hour and the refresh token
thirty days: the first refresh succeeded and disarmed every one after it.
It now keeps what it holds when the answer carries none.

Nothing reacted to `UNAUTHENTICATED`. `clearTokens()` was called from the
sign-out button and nowhere else, so the guard kept admitting a stale token
and `useLiveSession.load()` rebuilt a session out of claims the API had
just refused. A token that cannot be renewed and a `401` are now the same
fact, handled once in the HTTP client, which ends the session and returns
the browser to the sign-in screen with the reason. An authentication
failure is no session; offline and a 500 still degrade.

And a query that had failed was rendered as a query still loading, because
`isLoading || !data` collapses two states whenever retries are off.
`queryState` separates the three, and the dashboard and the template cards
use it.

RN-SUB-022 is written in `software-vision.md`, with the interface rule in
13.2 and the token lifecycle in `architecture-guide.md` 5.3 and 8.5.

Closes #53

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
`whoami` ended the folder-identifier paragraph with `(RN-AGT-020)`, and the
vault role-limit validation answered with `(RN-ACC-012)` in the message it
returns. Both codes name a line of a document the reader of the MCP surface
does not have: there the code resolves to nothing, and an agent either drops
it as noise or reads it as something addressable and spends a step on it.
Neither sentence loses anything without it.

The traceability stays where the other ~50 occurrences already are, in
comments and docblocks next to the implementation. A test now walks
everything the connector serves — the whoami text with and without a vault
to reach, every tool title, description and input schema, every skill, and
every entry of the declared notation — and fails on a rule code in any of
them, so the boundary is not a habit.

RN-AGT-021 is written in `software-vision.md`.

Closes #52

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
Every visit to a vault started at the tree, and getting back to the note
open in the previous session meant walking the folder structure again.

The path of the last note opened is remembered per vault, in
`localStorage`, and arriving at the vault navigates to it. Remembering it
across devices is deliberately not delivered: it would be a write on every
note opened, on the hottest path of the reading surface, against the quota,
and carrying an `Authorship` that reading does not have.

Two properties keep it from becoming a trap. It fires on ARRIVAL and not on
request, so the vault name in the sidebar still reaches the Vault Context
instead of bouncing back into the note. And the remembered path is checked
against the structure the layout already holds before anything navigates,
so a note deleted, renamed or moved is forgotten in silence and the tree is
shown — no request, no flash, no not-found line.

`noteAt` moves to `trail.ts`, where the route and the resume ask it the
same question rather than answering it apart. Every read and write of
storage is inside a try/catch, and a browser that refuses site data behaves
exactly as before.

Closes #72

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
…ing one

The notation used to be declared here, in `RECOGNISED_NOTATION`. It is now
the MemorySmith Markdown Profile 0.1.0, a specification with a version of
its own, and this build implements a version of it and says which.

The version is pinned ONCE, in the `catalog:` of `pnpm-workspace.yaml`. Two
packages read it from there -- the contracts, which project `profile.json`
into `RECOGNISED_NOTATION`, and the frontend, whose reading surface is
proved against the same cases -- and a catalog is what keeps them from
pinning two versions of one specification.

The Discovery conformance test runs the PUBLISHED cases now, and not a
transcription of them: it asserts links and facets per case, and it checks
that every notation of the two extractors has a case, so the profile cannot
grow an entry nothing proves. It also caught the difference between
`recognised: false` and "discarded", which the old blanket assertion had
collapsed: `title:` is not reserved AND is an ordinary attribute.

The reading surface gets a conformance test of its own kind, because a
rendering assertion cannot live in a JSON file. It renders each
`reading-surface` entry through the real components, and a declared entry
with no expectation written for it fails. On its first run it found #71:
`TaskItem` reads `node.checked`, which react-markdown 9 does not put on a
hast node, so every task box in the product renders as GFM's own disabled
checkbox.

`get_skill` opens by naming the profile, its version and its address, with
the three rings, and the notation table carries the ring of each form.

RN-AGT-022 and RN-AGT-023 are written in `software-vision.md`, the three
layers and how the profile enters the build in `architecture-guide.md`
11.0, and `knowledge-base.md` 1.1 now says that CommonMark and GFM are the
whole list and that every tool renders outside its own spec, GitHub
included.

Closes #61

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
…s it

Clicking a checkbox changed nothing, on any note, in any vault, whatever
the effective role. Neither of the two paths the issue listed was it.

`TaskItem` decided with `node.checked`. react-markdown 9 hands a component
the HAST element, which has no `checked`: the state lives on the child
`input` it generates. So the value was `undefined` for every item, the
component took its first branch, and what reached the screen was the
checkbox GFM renders by default, `disabled`, with no handler on it. The
ordinal machinery was never reached and was never at fault --
`node.position.start.offset` is present and correct.

Three things had to change together:

- the state is read from the child `input`, not from the item;
- that input is dropped from the children, because ours replaces it and two
  boxes in one line is worse than none;
- our class is applied AFTER the spread, because `task-list-item` arrives on
  `className` and was overwriting `task-item`.

`WritableContent` now states the text the ordinal is counted over instead of
leaning on a default, and refuses to enable the boxes when the rendered text
and the original disagree on how many there are. That is not theoretical:
`- [[x]]` resolves to `- [x](...)`, a ticked box to any GFM reader, where the
bytes have none -- counting on one side and writing on the other would
toggle a different item in silence.

Found by the reading-surface conformance test added in dd938e5, on its first
run, which is the second defect the profile has surfaced this cycle.

Closes #71

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
The product had two doors into the sign-in page and they behaved
differently, with nothing on screen to explain why. Arriving without a
session handed the browser to the provider; signing out stopped at a card
with a button that asked for nothing, because the provider owns the
credentials.

The comment defending that asymmetry named a loop: handing the browser back
to a still-warm provider session would sign the person straight in again.
It was verified against the deployed pool before this was written, and it
does not happen. `signOut()` goes through the Cognito `/logout` endpoint,
which ends the hosted-UI session, and the handover that follows lands on a
credentials form. There was nothing left for the click to protect.

The `signedOut` mark is gone entirely rather than being repurposed: with the
sign-out path behaving exactly as a first arrival, the only job it had left
was lowering the handover guard, which is what `clearHandover()` already
does and what the sign-out path now calls. That keeps the guard armed on the
way out, so a handover that comes back empty still stops instead of looping.

The `auth.signedOut` string is removed from both locales, since nothing
renders it any more.

The two branches that keep the button are untouched and now pinned:
`withoutSubscription`, which is the one fact a person cannot discover any
other way, and `handover`, which is the loop guard itself. The decision is
extracted as `decideLogin` so the rule is a test rather than a shape in an
effect.

Closes #59

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
…ffect

`tags`, `created` and `updated` worked by accident of shape and nothing said
so anywhere, while the product declared only `maturity` and `reviewed`.

The four reserved keys are now named, in en-US, and reserving is DECLARING
rather than enforcing: the extractor still classifies them by the shape of
the value, so `created: manually` degrades to an ordinary enum and nothing
is refused. `title` stays unreserved, because the title of a note is
structural and the cardinality ceiling already switches a `title:` key off
on its own.

Two of the four reach past the counts.

A facet of kind `date` matches by PREFIX. The values are canonicalised to
`YYYY-MM-DD`, so a prefix is exactly the granularity somebody asked for, and
substring let `created:09` mean September and also the year 2009. The kind
travels with the note into the content index to make that possible, and it
is optional there: an item written before it existed keeps answering by
substring instead of the search going silent while a projection is rebuilt.

`aliases` join the search index as other spellings of the note, answering
wherever the title does -- under `title:` and under a bare term -- and
ranking as a title hit rather than as a mention in a paragraph. They do NOT
resolve wikilinks: that is #65, it is a different animal, and shipping it by
accident here would put an attribute of the content in charge of behaviour
without the decision ever being taken.

The interface may translate the LABEL of a reserved key and never the bytes,
which is the same line PP4 draws everywhere else.

RN-DSC-030, RN-DSC-031 and RN-DSC-032 are written in `software-vision.md`,
and the shape rule is stated in `architecture-guide.md` 11.3 where a reader
finds it rather than only in a code comment.

Closes #62

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
`#subject` in the body looked like it organised something and organised
nothing. The entry and its conformance cases arrived with the profile in
dd938e5, so what was missing was the rule and the half of it the reading
surface owns.

RN-DSC-033 is written, with the reason rather than only the outcome: two
lineages read the inline hashtag incompatibly, as a link and as file
metadata, so there is nothing to inherit and any choice diverges from
somebody. Here the curation vocabulary lives in the frontmatter, under the
Guidance. Reading it would need a third sanctioned reader of content and a
real Markdown parser in the backend, against PP4, and in the 1,562 notes of
the example vaults one of the three inline matches was a hex colour.

The reading surface is now held to the rejection by tests, because that is
where a rejection is most easily undone by accident: a chip drawn around
`#subject` would promise a grouping that does not exist. The external link
is pinned in the same place, for the same reason.

Rejecting is not refusing: the bytes are stored and returned as written.

Closes #63

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
The granularity of a point was covered and an interval was not, which is
the ordinary question of curation: what came in during the first quarter,
what has not been revised since March, what was written before a decision.

This is the FIRST operator the query language has, so the decision is not
only about dates. The comparison is the primitive, because it composes with
the boolean operators already there and needs no second syntax for an open
interval. The range `a..b` is kept because it covers the common case in one
token, and it is defined as SUGAR: it desugars into `>=a` and `<=b` at parse
time, so everything past the parser sees one shape and there is one
semantics to implement, to test and to explain. Both ends are inclusive,
which is what `..` means everywhere a person has met it before.

Comparison cuts the value to the length of the operand before comparing,
which is what keeps the prefix granularity of RN-DSC-031 usable as an
interval end: `created:<=2026-02` includes the fifteenth of February instead
of excluding most of the month. That off-by-one would never have been
reported, because it looks like an empty result.

Two things are refused rather than answered empty. A range with inverted
ends is a syntax error at parse time. An interval over an attribute this
vault does not hold as a date is refused in `SearchNotes`, once, against the
kinds the scan already carries -- because whether an attribute is a date is
a fact about the vault and not about the query string. An empty result reads
as "there is nothing filed under that", and neither of these means that.

RN-DSC-034 is written in `software-vision.md`, and the precedent the shape
sets is in `architecture-guide.md` 11.3.

Closes #64

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
Every wait in the product rendered one of two things and neither said
anything: a blank 60vh of viewport, which is what every page load and every
deep link showed first, and one line of grey text standing in for a whole
screen.

Eight surfaces now draw a placeholder matched to the frame the real content
occupies: the application while the session resolves, the vault catalogue,
the KPI tiles and the facet charts, the note, a template card, an embedded
block mid-paragraph, the folder tree and the graph canvas. One reusable
primitive composes all of them, because a generic grey box used everywhere
and eight hand-rolled ones are the same failure from opposite directions.

`VaultLayout` is the one that pays for itself first. Its frame -- sidebar,
brand, search box, navigation, content column -- is known before the request
leaves and was being withheld for a structure query, so the whole screen
arrived at once, with a jump. It now draws immediately and skeletons only
what the query fills.

The graph stops swallowing its own failure. It used to catch and render an
empty vault, so "nobody has written anything" and "we could not read it"
looked identical.

A placeholder belongs to `isPending` alone, which is what #53 made possible:
`queryState` reads the error first, so a shimmer can never sit over a
request that already failed. Every region carries `aria-busy` and keeps the
word for whoever hears the page, since a shape announces nothing where the
old line at least said "Loading". The sheen stops under
`prefers-reduced-motion`, leaving the frame in place.

`.loading-screen` is deleted: nothing renders it any more.

Closes #58

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
…ences

The profile bumped to 0.2.0 and the reading-surface conformance test failed
on seven declared entries with no expectation written for them, which is the
guard doing its job: the profile cannot grow a notation this interface
silently does not implement.

`==highlight==`, `%%comment%%` and `^block-id` were already written in the
example vaults and did nothing. All three are remark plugins and not passes
over the string, because a `==` inside a code fence is not a highlight and
only the parser can tell. The comment leaves the PAGE and stays in the
bytes, so `read_note` returns it and the export writes it: an agent sees
what a person on the page does not, and that asymmetry is declared rather
than discovered. `![[note#^id]]` resolves to the identified block, told
apart from a section anchor by the marker rather than by trying one and
falling back.

Math renders through remark-math and KaTeX, with the rule the profile states
and the library does not implement. remark-math opens a formula at any `$`
and closes it at the next, so `costs $30 a month, and the plan $60` turned
the sentence between the prices into mathematics. `remarkMathDollarRule`
runs after it and gives back any inline formula whose delimiters break the
rule, reading the source through the node position because the delimiters
are gone from the node by then.

Raw HTML stays unrendered and is now declared, with the payload that
matters: a note carrying `<script>`, an `onerror` handler or an `<iframe>`
is escaped and shown as text. Superscript and subscript follow from it and
have no notation, and GFM's single-tilde strikethrough is switched off for
the same reason: left on, `H~2~O` written for a subscript came out struck
through, which is a worse answer than nothing.

RN-DSC-035 is written for the block reference; the rest are 13.2 in prose,
as interface rules are, and the plugin stack is in `architecture-guide.md`
11.0.

Closes #66
Closes #67

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
A vault written in an editor that reads `#subject` as metadata arrives here
intact and loses the curation those tags carried. That is the declared cost
of the rejection in #63, paid at the door by whoever is arriving, and it is
worth offering to pay it for them.

The issue assumed a capability in the product that reads the inline tags and
proposes the frontmatter. That cannot be built here without breaking design
rule 5: reading `#subject` for meaning makes the backend a THIRD sanctioned
reader of content, which is the exact guarantee the rejection was made to
protect. Buying back the cost of a rejection by spending the reason for it
is not a trade this product makes. The interface may not do it either --
13.2 keeps it on the same side of PP4.

So the product teaches the method and the agent performs it, which is the
shape #21 already established and which the thesis of the product asks for.
`get_skill` serves `convert-inline-tags`:

- what is NOT a tag, in a table: a heading, a hex colour, `C#`, an issue
  number, a URL fragment, anything inside code;
- propose per note and in full, showing what would be written AND what was
  rejected, because a rejection you got wrong is invisible unless said;
- wait for a person to accept, with "proceeding unless told otherwise" named
  as the failure mode rather than left implicit;
- write one note at a time with `update_note` and its `baseRevision`, so
  each conversion is an ordinary authored write with a revision of its own,
  and design rule 10 is satisfied by construction: there is no batch;
- leave the body alone, because the inline tag is the author's bytes and
  leaving it makes the whole thing reversible by deleting one line.

Every one of those is a test, because a method that teaches half of them
would put an agent halfway through a migration nobody agreed to.

RN-PRT-007 is written, with the reason it is a skill and not an endpoint.

Closes #68

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
The notation was proved in tests and taught by a skill, and none of that can
be read by a person deciding whether to bring their knowledge here. Neither
shows it doing its work: a callout that is drawn, an alias that finds a note
by its acronym, an embed that expands to a single identified block, a
formula, a checklist that writes back.

`continuity-engineering` and `enologia` do. Every declared notation of the
three rings appears in each of them IN CONTEXT -- the acronyms are the
acronyms those subjects live on, the formula is the arithmetic the subject
actually uses, the checklist is a runbook -- and so does every rejection,
written where somebody would have reached for it and beside the sentence
saying what to write instead. That is the half of the profile no other vault
will ever show, because a vault written by somebody using the product only
contains what worked.

They are NOT translations of each other. Continuity objectives and enology
carry the same notations through different material, which is what lets the
pair show what one vault cannot: the four reserved keys are in en-US in
BOTH, while `regiao`, `tipo` and `colhida_em` stay in the language of
whoever keeps the vault.

The guard runs in both directions, and it is the whole reason a hand-written
vault is allowed to be the artefact here: every entry of the declared
notation appears in each vault, and neither demonstrates a notation the
profile does not declare -- a dataview block, a footnote, a templater
expression, an inline field. A declared entry with no detector fails the
test rather than being noticed later, which is the same shape the
reading-surface conformance uses.

They go up and come down with the environment through the existing scripts,
with no step of their own.

Closes #69

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
`[[Lei 14.133]]` is a real edge: the backend resolved it, the backlink
exists, the graph carries the connection. The reading surface drew it as a
pending link, telling the person a note they had written did not exist.

The slug is computed twice. The kernel produces it for storage and for the
link extractor; the interface produces it again to turn a wikilink into a
URL, because the frontend takes types from `@memorysmith/contracts` and
nothing else from the backend. The second copy implemented four of the six
steps of the profile 3.3, missing the `.` or `,` BETWEEN TWO DIGITS -- which
belongs to the number and not to the words around it -- and the truncation
at eighty characters. So the interface addressed `lei-14-133`, found nothing
under it, and rendered a link that resolves as one that does not.

It fired wherever a title carries a decimal number, which is where these
vaults live: norms, articles, versions, measurements. The two demonstration
vaults added in the same cycle are full of them.

The two implementations are NOT pinned to each other. Both are pinned to the
published conformance cases, which is the only arrangement where the drift
is a failing build instead of a screen that lies. Nine of them fail the
moment the rule drifts again -- and one, `wikilink/accents-and-digits`, was
already in the suite and had never been pointed at this side of the product.
That is the fifth defect the profile has surfaced in this cycle.

Closes #73

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
Steps 1 to 5 of the version bump flow (development-process.md 9.1): the base
version in CLAUDE.md, the thirteen package.json of the monorepo, and the
CHANGELOG section renamed with the cut date, with a fresh [Unreleased] above
it and the compare links at the bottom. The "Current state" paragraph of
CLAUDE.md is rewritten for the cycle.

Minor and not patch, and the accumulation decides it rather than any single
pull request: `RECOGNISED_NOTATION` and the skill now come from a
specification versioned outside this repository, the connector serves a new
skill, the query language gained its first operator, the frontmatter gained
a reserved vocabulary with two keys that do something, and the reading
surface gained the rest of the vault ring. While the base version is 0.x a
contract break lands in a minor, which is what the profile import is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
Two checks failed on the pull request, and both passed here for reasons that
say more than the failures.

`shared/api/source.ts` throws at module load when `VITE_API_ORIGIN` is
missing, which is right for the application: the interface has no offline
mode, and failing at the door beats failing per screen. But four test files
added in this cycle reach that module through a component, so they passed on
a machine with a `.env.local` and failed on one without. The test run now
declares its own origin in `vite.config.ts`. The value is never used --
nothing under test reaches the network -- and it lives there so no test file
has to know and no CI environment has to be told.

The lint error is a helper I wrote in the demonstration-vault guard and
never used. It was in front of me the whole time: my local check filtered
eslint output for " error ", and the `no-console` warning text contains
"warn, error", so `head -3` cut the output before the real error line. The
filter was matching the noise and hiding the signal.

Verified by reproducing CI rather than by trusting the filter: `.env.local`
moved aside, `pnpm lint` at zero errors and the whole suite green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
Removing the unused helper left the file outside prettier's shape, and CI
runs `pnpm format` as a check. That is the third thing my local verification
did not cover, after the lint filter that hid its own signal and the
`.env.local` that made the frontend tests pass here and fail there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAMCXmvUPsRoPzj9AQMijZ
@heitorrapcinski
heitorrapcinski merged commit 2f23f9f into main Sep 6, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment