Skip to content

feat(take-notes): add a manual tag vocabulary and gallery tag chips - #4

Merged
davertor merged 6 commits into
mainfrom
davertor/tag-vocabulary-and-gallery
Aug 20, 2026
Merged

feat(take-notes): add a manual tag vocabulary and gallery tag chips#4
davertor merged 6 commits into
mainfrom
davertor/tag-vocabulary-and-gallery

Conversation

@davertor

@davertor davertor commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Adds a manually curated tag vocabulary so the archive can answer "show me my AI notes" — today the gallery offers only a free-text filter over title/byline/meta, so browsing by topic means remembering a word that happens to appear in the text.

The vocabulary is closed: it lives in ~/take-notes/config.json, the skill picks from it and never invents a tag, so the taxonomy stays the user's rather than whatever the model felt like that day. Default state is a single tag, Unknown, which is also the silent fallback when nothing fits.

{ "language": "es", "tags": ["Unknown", "AI", "Investing", "Engineering"] }

How it looks in the gallery

docs/gallery-tags.png — added in this PR, and rendered inline in the README's new Tags section.

(The repo is private, so GitHub can't proxy the image into this body. The Files changed tab shows it too.)

Two things are new on this page:

  • A chip per tag in use, under the filter bar. Click one to narrow the grid, click it again to clear. The chip goes pen-coloured while active and the hit counter reads 2/4. Chip and text filter combine with AND — the existing apply() grew one piece of state rather than a second filtering engine.
  • The primary tag on each card, beside the kind in the foot (VIDEO AI, ARTICLE ENGINEERING). Extras don't show there but are still filterable: every tag goes into data-tags, folded and pipe-delimited at both ends so a whole tag matches without ai also hitting air gap.

The four notes are the real committed examples; the tags on them are illustrative, since the vocabulary is yours to define. Re-rendering docs/examples/ with genuine tags — and rebuilding the Pages gallery at docs/index.html, which predates chips — is deliberately left out of this PR.

Notes written before tagging existed parse as untagged and group under the Unknown chip, so nothing falls out of the grid.

What's in it

scripts/notes.py becomes the single config reader (read_config, configured_tags) and parses the rail's tag row back out; Note gains tag + tags
scripts/tags.py (new) uv run tags.py [--add X] [--remove X] — read-modify-write, so language survives; refuses to remove Unknown
scripts/render.py repeatable --tag, first occurrence is primary; stays a dumb renderer and validates nothing
scripts/gallery.py the chips, the card tag, data-tags
scripts/export.py tags reach Obsidian's frontmatter tags: list and the Anki tags column
SKILL.md Step 0 for tag management, the selection rule in Step 2, --tag in Step 5
both note templates {{TAGS}} after the .watch link, plus .tags/.tag/.tag.is-primary styling

Managing the vocabulary works from either side — /take-notes --tags | --add-tag "AI" | --remove-tag "AI", or tags.py directly. Both are worth having: the skill is disable-model-invocation: true, so saying "add a tag" in ordinary conversation never reaches it.

Verified

for s in render notes gallery export transcript tags; do uv run skills/take-notes/scripts/$s.py --selftest; done — all six pass, and tags.py is now in the CI loop.

  • No regression on untagged notes — the four committed examples still render, grouped under one Unknown chip.
  • CLI round-trip--add AI --add Investing, then --remove Investing; "language": "es" intact throughout, and Unknown refused removal.
  • Render → parse--tag AI --tag Engineering reads back as tag == "AI", tags == ("AI", "Engineering").
  • Chips — the template's own filter IIFE driven under node against a DOM stub: narrow, clear-on-second-click, non-primary tags filterable, chip+text ANDing, single-select.
  • Exportstags: ["take-notes", "AI", "Engineering"] in the frontmatter; take-notes key-point AI Engineering in the Anki column, whitespace hyphenated since Anki splits that field on spaces.

Still open: /take-notes --add-tag and a full end-to-end run against a real source, both of which need the skill symlink pointing at this branch.

🤖 Generated with Claude Code

davertor and others added 6 commits August 20, 2026 13:01
Browsing the archive meant remembering a word that happens to appear in a
note's text. Tags give it a spine.

The vocabulary is closed and hand-curated in ~/take-notes/config.json: the
skill picks from the list and never invents a tag, so the taxonomy stays the
user's rather than the model's. `Unknown` is always present as the silent
fallback, which is also the default state.

notes.py becomes the single config reader — one place to decide what a
malformed file means — and learns to parse the rail's tag row back out, so an
untagged note written before this still parses as ("", ()).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The payoff of tagging. A chip per distinct tag under the filter bar: click to
narrow, click again to clear, and it ANDs with the text filter rather than
replacing it — the existing apply() grew one piece of state instead of a
second filtering engine.

Cards carry every tag in data-tags, folded and pipe-delimited at both ends so
a whole tag matches without "ai" also hitting "air gap". Notes with no tag of
their own group under Unknown, which keeps the committed examples reachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tags that stop at the gallery are half a feature. The Markdown frontmatter
lists them after take-notes, which is what Obsidian's tag pane reads, and
they join the card kind in the Anki tags column so a deck filters by topic.

Anki splits that field on whitespace, so a two-word tag is hyphenated on the
way out or it silently becomes two unrelated tags.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Tags section beside Language, since both are config.json concerns: the
closed vocabulary, the silent Unknown fallback, and both ways to edit it.
The Gallery and Export sections gain the line each was missing.

CONTRIBUTING gets .tags/.tag in the masthead-contract table — notes.py parses
it now, so renaming those classes breaks the parser, which is the point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A script with its own asserts that nothing runs is a script with no asserts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Screenshotting the gallery caught it: `overflow: hidden` on the tag resolves
its flex min-width to 0, so it became the item that shrank and rendered as
"ENGINEE…" — a clipped label defeats the whole point of putting it there.

Both labels keep their intrinsic width and the foot wraps instead, dropping
the date onto its own line when the row is full. `justify-content:
space-between` goes with it: the date's margin-left:auto already pushes it
right, and space-between flung the tag away from the kind once the row wrapped.

Adds the screenshot the README's new Tags section points at.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@davertor
davertor merged commit 4c91a75 into main Aug 20, 2026
1 check passed
@davertor
davertor deleted the davertor/tag-vocabulary-and-gallery branch August 20, 2026 11:10
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