Skip to content

[#13] Optional: render topics as Obsidian #hashtags#27

Merged
VGonPa merged 1 commit into
developfrom
ws-13-hashtags
May 22, 2026
Merged

[#13] Optional: render topics as Obsidian #hashtags#27
VGonPa merged 1 commit into
developfrom
ws-13-hashtags

Conversation

@VGonPa
Copy link
Copy Markdown
Owner

@VGonPa VGonPa commented May 22, 2026

Closes #13.

Summary

Adds an opt-in config flag, [output] topic_style = "wikilink" | "hashtag", so
the in-body **Topics:** line on each item note can be rendered as Obsidian
hashtags instead of wikilinks. Default is wikilink — fully
backwards-compatible.

  • wikilink (default): **Topics:** [[ai-coding]] · [[software-engineering]]
  • hashtag: **Topics:** #ai-coding #software-engineering

Frontmatter tags:, the _index.md ## Topics ranking and the topic-page
post lists are unchanged — those are navigational by design and stay
wikilinks regardless of topic_style.

Decisions (resolved in the PRD)

  1. Replace, not alongside. Hashtags already navigate when a same-named
    note exists (every topic page is topics/<slug>.md); "alongside" doubled
    the line noise without paying for itself.
  2. Flag name: [output] topic_style — sits next to the existing
    [output] language. Backwards-compatible default "wikilink".
  3. Slug compatibility: kebab-case slugs (^[a-z0-9]+(?:-[a-z0-9]+)*$,
    per models.py:Topic.slug) are a strict subset of Obsidian's tag
    charset [A-Za-z0-9/_-], so no slug change is needed.
  4. Frontmatter: unchanged in both modes (the orthogonality invariant is
    asserted in test_generate_renders_topics_as_hashtags_when_requested).

Implementation

  • Config.topic_style: str (src/xbrain/config.py) — read from
    [output].topic_style, defaults to "wikilink", validated against
    SUPPORTED_TOPIC_STYLES = ("wikilink", "hashtag"). Unknown value →
    clean ValueError listing the supported set.
  • generate(..., topic_style: str = "wikilink") — threads the value
    through to _enrichment_lines. Defensive ValueError at the entry
    point for programmatic callers (mirrors how output_language is
    validated by strings_for() on entry).
  • cli.py:_run_generate passes cfg.topic_style through.
  • config.toml.example and the README config table + TOML block document
    the new key. README Layer 1 gets a one-sentence note pointing to the
    hashtag mode near the existing example.
  • No new module — topic_style is a layout choice, not a locale concern,
    so it stays on Config rather than entering xbrain.i18n.

Tests

7 new tests, all green:

  • tests/test_generate.py
    • test_generate_renders_topics_as_wikilinks_by_default — default mode
      keeps the exact current line.
    • test_generate_renders_topics_as_hashtags_when_requested — same
      item, hashtag mode, asserts the body line + frontmatter unchanged.
    • test_generate_hashtag_mode_does_not_affect_index_or_topic_page_lists
      — the ## Topics ranking still uses wikilinks.
    • test_generate_rejects_unknown_topic_styleValueError listing
      supported values.
  • tests/test_config.py
    • test_load_config_defaults_topic_style_to_wikilink
    • test_load_config_round_trips_hashtag_topic_style
    • test_load_config_rejects_unknown_topic_style

Quality gate (uv run poe check)

✅ Ruff (linting)       PASS
✅ Ruff (format)        PASS
✅ Mypy (types)         PASS
⚠️  Radon (complexity)   WARN - Has grade C   (pre-existing, not introduced by this PR)
✅ Bandit (security)    PASS
✅ Vulture (dead code)  PASS
✅ Interrogate (docs)   PASS - 73.5%
✅ Detect-secrets       PASS
✅ Tests                PASS - 306 tests
✅ Coverage             88%
✅ Deptry (deps)        PASS
ALL CRITICAL CHECKS PASSED

Out of scope

  • CLI flag --topic-style for one-off overrides (file a follow-up issue if
    needed).
  • Switching the default from wikilink to hashtag.
  • Changing topic-page or index rendering.

Add `[output] topic_style = "wikilink" | "hashtag"` config flag (default
`wikilink`, backwards-compatible). When set to `hashtag`, the in-body
`**Topics:**` line is emitted as `#ai-coding #software-engineering`
instead of `[[ai-coding]] · [[software-engineering]]` — so the line is a
tappable Obsidian tag in addition to the frontmatter `tags:` entries.

Scope is the in-body line only. Frontmatter `tags:`, `_index.md`'s `##
Topics` ranking and topic-page post lists keep wikilinks — those are
navigational by design.

The flag is validated at config-load time and re-validated at the
`generate()` boundary so programmatic callers (tests, notebooks) fail
fast on a typo.

PRD: docs/prds/2026-05-22-xbrain-13-hashtag-topics.md (in vault)
Plan: docs/implementation-plans/2026-05-22-xbrain-13-hashtag-topics.md

Tests:
- 4 new in test_generate.py: wikilink default, hashtag mode, hashtag
  mode does not bleed into the index, unknown style raises.
- 3 new in test_config.py: defaults to wikilink, hashtag round-trips,
  unknown style rejected.

Quality gate: all critical checks pass · 306 tests · 88% coverage.
@VGonPa VGonPa merged commit 80b830d into develop May 22, 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