Skip to content

feat(docs): make chat links outlive the server, the path, and the title - #58

Merged
arthware-dev merged 2 commits into
mainfrom
feat/one-link-builder
Aug 2, 2026
Merged

feat(docs): make chat links outlive the server, the path, and the title#58
arthware-dev merged 2 commits into
mainfrom
feat/one-link-builder

Conversation

@arthware-dev

Copy link
Copy Markdown
Contributor

Every link the archivist posted into a room carried the document server's address as it looked at that moment. Chat history is append-only, so changing the domain, switching between port and domain mode, or moving Paperless left every one of those links dead, with no way to rewrite them.

Document links now point at the stack's own /go resolver, which looks up where the document lives at click time. The resolver already existed and only the todo links used it; the five places that built an address by hand now share one builder in lib/stack/links.py.

Two things this deliberately does not cover: document pages mirrored into the vault still store an absolute address (a vault-format change, following separately), and memory search hits still link to the Forgejo file view, since /go has no route for an arbitrary vault path.

Every link the archivist posted into a room carried the document
server's address as it looked at that moment. Chat history is
append-only, so changing the domain, switching between port and
domain mode, or moving Paperless left every one of those links dead,
with no way to rewrite them.

Links now point at the stack's own resolver, which looks up where the
document lives at the moment someone clicks. The resolver already
existed and only the todo links used it; the five places that built
an address by hand now share one builder.

Document pages mirrored into the vault still store an absolute
address. That is a change to the vault format and follows separately.
@arthware-dev

Copy link
Copy Markdown
Contributor Author

Verified on the demo rig with the real archivist. Searching Duff Insurance in #documents now returns resolver links:

http://192.168.188.42:42000/go/docs/77
http://192.168.188.42:42000/go/docs/78

Before this branch the same search produced direct Paperless URLs, e.g. http://192.168.188.42:42020/documents/101/details, which are still visible higher in that room's history.

Unit lane: 2200 passed, 8 skipped. ruff clean. Rebased onto current main; the duplicate .gitignore chore this branch carried dropped out automatically as already-upstream.

Two limits worth a reviewer knowing, both visible in the same search output:

  1. Memory hits are unchanged. A note or bookmark result still links to the Forgejo file view (.../src/branch/main/family/camping/bookmarks/...) with the server address embedded, because /go has routes for docs, topic and person but none for an arbitrary vault path. Adding one would be the follow-up that closes this properly.
  2. The resolver's own address is still baked into chat history. This collapses many moving addresses into one, and that one is only stable in domain mode. In port mode http://<lan-ip>:42000/go/docs/77 dies with the next DHCP lease exactly like the old links did.

Unrelated pre-existing gap noticed while testing, not touched here: the topic-room welcome promises ?<query> searches the topic, but routing only searches when the bot is mentioned or the room is #documents (archivist.py:1718), so a ?-prefixed message is silently ignored.

A search hit for a note or bookmark linked straight into Forgejo, at
the path the file happened to have that day. That path carries the
bucket, the topic slug and the title slug, and all three change under
ordinary use: a capture re-scopes when a second person joins the room,
a topic gets renamed, a correction rewrites the title. The link then
breaks silently, still looking valid.

Captures now get `/go/capture/<capture id>`, resolved at click time by
finding the file that carries that id. The id is the Matrix event the
capture came from, assigned once and never rewritten, so the link
survives every one of those moves.

This settles which links the namespace takes: entities (topic, person)
are addressed by name, records (docs, capture) by an id that is never
re-derived, and nothing is addressed by path. Pages with no id, such as
hand-written wiki entries, keep the old link until they have one.
@arthware-dev arthware-dev changed the title fix(docs): keep links posted in chat working after the server moves feat(docs): make chat links outlive the server, the path, and the title Aug 2, 2026
@arthware-dev

Copy link
Copy Markdown
Contributor Author

Second commit added: /go/capture/<id>, which closes the gap I flagged above.

The decision it settles, since a link namespace is permanent: kinds come in two families. Entities (topic, person) are addressed by name. Records (docs, capture) are addressed by an id that is never re-derived. Nothing is addressed by path — a path encodes the bucket, the topic slug and the title slug, and all three change under ordinary use, so a path-keyed link breaks for three reasons the resolver cannot repair, and breaks silently.

No index. find_capture scans brain for the id, which is the same tree the wiki serves, so it cannot disagree with what it links to and there is nothing to rebuild or invalidate. 65 files here, hundreds to low thousands in a real vault, and it runs when a human clicks. An index can go behind that function later without touching the resolver or the link format.

Verified on the rig. Search in #documents now posts:

http://…/go/docs/77
http://…/go/docs/78
http://…/go/capture/%24GAVzCJ41gDLHeD2WoMWjZsq1DzkxdTWKQmwcHZ2elqA
http://…:42040/family/memory/src/branch/main/family/correspondents/README.md   ← no id, keeps the old link

and that capture link resolves:

302 → http://…:42070/family/camping/bookmarks/2026/08/campfire-construction-safety-and-historical-context-36934e

Regression check on every existing route, all still 302 correctly: docs/77, topic/camping, topic/family/camping/todo, person/homer. Unknown capture id 404s rather than redirecting to something nearby.

Unit lane: 2211 passed, 8 skipped. ruff clean.

Two things worth a reviewer's eye:

  • search_format keeps the Forgejo blob URL for hits with no capture_id (hand-written pages like family/correspondents/README.md). Deliberate, so nothing regresses, but that link still rots. Correspondents are entity pages now, so /go/correspondent/<slug> is the natural follow-up.
  • The tools container mounts brain via ${DATA_DIR}/memory/brain, not ${BRAIN_REPO_DIR} — that variable is the path the bot-runner sees (/data/memory/brain), and using it as a bind source silently mounts an empty directory. Caught that on the rig.

@arthware-dev
arthware-dev merged commit d01b02a into main Aug 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant