diff --git a/AGENTS.md b/AGENTS.md index b0f484d..7945cfb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,12 +52,17 @@ making changes; it is the authoritative operations and deployment runbook. - Preserve normal links to dedicated tag pages as the no-JavaScript baseline. Client-side filtering must remain a progressive enhancement with semantic buttons, keyboard support, and an announced result count. -- Tag-directory counts and dedicated tag pages must include projects, blog - entries, and articles in the same discovery feed. +- Tag-directory counts and dedicated tag pages must include projects, their + devlog entries, blog entries, and articles in the same discovery feed. - For remote projects, keep repository language labels and project-declared technology labels separate from their derived controlled tag slugs. Approved labels link to tag pages; unapproved labels remain visible, non-clickable, and mustard-colored but do not affect tag feeds or counts. +- Apply that same controlled mapping to remote devlog topics. Approved topics + link to tag pages and populate `topic_tag_slugs`; unapproved topics remain + mustard labels and are excluded from discovery. +- Keep project devlog entries in the homepage Recent Activity feed alongside + projects, blog entries, and articles, ordered by publication date. - When `README.md` changes, keep the README-derived example blog entry aligned with the runbook content it demonstrates. @@ -78,9 +83,9 @@ making changes; it is the authoritative operations and deployment runbook. the relevant cache-busting version in the template and verify that generated pages reference the new asset URL. - For taxonomy changes, verify every entry tag has a matching `content/tags/` - record, tag counts match rendered project/blog/article usage, dedicated tag - URLs build, and listing filters work with valid, absent, and unknown query - values. + record, tag counts match rendered project/devlog/blog/article usage, + dedicated tag URLs build, and listing filters work with valid, absent, and + unknown query values. - Run `git diff --check` and review the exact diff before committing. - If a push was requested, verify the Gitea Action result, deployed revision, and site health instead of treating a successful `git push` as completion. diff --git a/README.md b/README.md index 29a8f2c..2cba691 100644 --- a/README.md +++ b/README.md @@ -144,27 +144,30 @@ entry before its tag record exists. To rename or retire a tag, update every referencing entry deliberately and verify that no old slug remains before renaming or removing its record. -`/tags/` lists every approved tag and counts its usage across projects, blog -entries, and articles. Dedicated tag URLs combine matching records from all -three sections in one chronological discovery feed. Tag links are normal links -and work without JavaScript. On the Projects, Blog, and Articles listing pages, -`assets/static/tags.js` progressively adds instant filtering and keeps the -selected filter in the `tag` query parameter. +`/tags/` lists every approved tag and counts its usage across projects, project +devlog entries, blog entries, and articles. Dedicated tag URLs combine matching +records from all four content types in one chronological discovery feed. Tag +links are normal links and work without JavaScript. On the Projects, Blog, and +Articles listing pages, `assets/static/tags.js` progressively adds instant +filtering and keeps the selected filter in the `tag` query parameter. Remote-project languages and declared technologies are normalized to slugs and matched only against this controlled vocabulary. Their approved matches become clickable tags and a deduplicated `taxonomy_tags` field used by filters, counts, -and tag pages. A label that has no approved record remains visible and -non-clickable in mustard, produces a build warning, and is excluded from tag -discovery. Remote data never creates a tag record automatically. +and tag pages. Devlog topics follow the same rule and populate derived +`topic_tag_slugs` used by tag feeds and counts. A label that has no approved +record remains visible and non-clickable in mustard, produces a build warning, +and is excluded from tag discovery. Remote data never creates a tag record +automatically. After taxonomy or filtering changes: 1. Run `python scripts/build_with_projects.py --output-path build`. -2. Verify `/tags/`, at least one dedicated tag URL, and all three section listings. -3. Confirm tag counts cover projects, blog entries, and articles; every stored - entry slug has a matching tag record; and unapproved project labels are not - counted. +2. Verify `/tags/`, at least one dedicated tag URL, the three top-level section + listings, and a project devlog index. +3. Confirm tag counts cover projects, devlog entries, blog entries, and + articles; every stored entry slug has a matching tag record; and unapproved + remote labels are not counted. 4. Test filtering with JavaScript enabled and confirm tag links remain usable without JavaScript. 5. Check keyboard operation plus desktop and narrow layouts. @@ -194,7 +197,9 @@ The public routes are `/projects/thinkloom/`, `/projects/thinkloom/devlog/`, and `/projects/thinkloom/devlog//`. The project repository owns the native Lektor records and approved images. This site owns their models, -templates, layout, repository-information card, and import policy. +templates, layout, repository-information card, and import policy. Imported +devlog entries also join the homepage Recent Activity feed, ordered with +projects, articles, and blog entries by publication date. `configs/project-sources.ini` is the allowlist. Each source declares a stable project ID, credential-free HTTPS Git URL, public web/API URL, and branch. Add @@ -235,8 +240,9 @@ The project card combines repository-owned content with public provider and Git metadata: source and README links, default branch, latest commit, license, languages, open issues, stars, forks, latest release, imported revision, and sync time. Approved language and technology labels link into the shared tag -taxonomy, allowing a project to lead visitors to related writing. API metadata -failure uses cached metadata and is identified in the +taxonomy, as do approved devlog topics, allowing either a project or a +development milestone to lead visitors to related writing. API metadata failure +uses cached metadata and is identified in the sync status; it never causes an unvalidated new snapshot to replace a working one. diff --git a/content/blog/example-deployment-runbook/contents.lr b/content/blog/example-deployment-runbook/contents.lr index 08bc6f2..40022a7 100644 --- a/content/blog/example-deployment-runbook/contents.lr +++ b/content/blog/example-deployment-runbook/contents.lr @@ -161,19 +161,20 @@ entry before its tag record exists. To rename or retire a tag, update every referencing entry deliberately and verify that no old slug remains before renaming or removing its record. -`/tags/` lists every approved tag and counts its usage across projects, blog -entries, and articles. Dedicated tag URLs combine matching records from all -three sections. Tag links are normal links and work without JavaScript. On the -Projects, Blog, and Articles listing pages, `assets/static/tags.js` -progressively adds instant filtering and keeps the selected filter in the `tag` -query parameter. +`/tags/` lists every approved tag and counts its usage across projects, project +devlog entries, blog entries, and articles. Dedicated tag URLs combine matching +records from all four content types. Tag links are normal links and work without +JavaScript. On the Projects, Blog, and Articles listing pages, +`assets/static/tags.js` progressively adds instant filtering and keeps the +selected filter in the `tag` query parameter. After taxonomy or filtering changes: 1. Run `python scripts/build_with_projects.py --output-path build`. -2. Verify `/tags/`, at least one dedicated tag URL, and all three section listings. -3. Confirm tag counts cover projects, blog entries, and articles and that every - stored entry slug has a matching tag record. +2. Verify `/tags/`, at least one dedicated tag URL, the three top-level section + listings, and a project devlog index. +3. Confirm tag counts cover projects, devlog entries, blog entries, and articles + and that every stored entry slug has a matching tag record. 4. Test filtering with JavaScript enabled and confirm tag links remain usable without JavaScript. 5. Check keyboard operation plus desktop and narrow layouts. diff --git a/content/tags/architecture/contents.lr b/content/tags/architecture/contents.lr new file mode 100644 index 0000000..ae0442e --- /dev/null +++ b/content/tags/architecture/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: Architecture +--- +summary: Product boundaries, system structure, and consequential technical design. diff --git a/content/tags/configuration/contents.lr b/content/tags/configuration/contents.lr new file mode 100644 index 0000000..062fa3a --- /dev/null +++ b/content/tags/configuration/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: Configuration +--- +summary: User-editable and system configuration design and implementation. diff --git a/content/tags/contents.lr b/content/tags/contents.lr index 7dc707d..db886ec 100644 --- a/content/tags/contents.lr +++ b/content/tags/contents.lr @@ -4,4 +4,4 @@ title: Tags --- eyebrow: Taxonomy --- -summary: Browse the controlled vocabulary used across projects, blog entries, and articles. +summary: Browse the controlled vocabulary used across projects, devlogs, blog entries, and articles. diff --git a/content/tags/documentation/contents.lr b/content/tags/documentation/contents.lr new file mode 100644 index 0000000..f6fdcb9 --- /dev/null +++ b/content/tags/documentation/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: Documentation +--- +summary: Technical, operational, product, and user-facing documentation. diff --git a/content/tags/interface/contents.lr b/content/tags/interface/contents.lr new file mode 100644 index 0000000..af2b207 --- /dev/null +++ b/content/tags/interface/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: Interface +--- +summary: Interaction models, workspace layout, and user-interface design. diff --git a/content/tags/json-schema/contents.lr b/content/tags/json-schema/contents.lr new file mode 100644 index 0000000..848a5d3 --- /dev/null +++ b/content/tags/json-schema/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: JSON Schema +--- +summary: Machine-readable JSON contracts, validation, fixtures, and compatibility. diff --git a/content/tags/models/contents.lr b/content/tags/models/contents.lr new file mode 100644 index 0000000..9757578 --- /dev/null +++ b/content/tags/models/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: Models +--- +summary: AI model integration, selection, configuration, and behavior. diff --git a/content/tags/mvp/contents.lr b/content/tags/mvp/contents.lr new file mode 100644 index 0000000..3bcff7a --- /dev/null +++ b/content/tags/mvp/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: MVP +--- +summary: Minimum viable product milestones and end-to-end prototypes. diff --git a/content/tags/openai-build-week/contents.lr b/content/tags/openai-build-week/contents.lr new file mode 100644 index 0000000..641c6b4 --- /dev/null +++ b/content/tags/openai-build-week/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: OpenAI Build Week +--- +summary: Projects, milestones, and releases developed during OpenAI Build Week. diff --git a/content/tags/privacy/contents.lr b/content/tags/privacy/contents.lr new file mode 100644 index 0000000..72377aa --- /dev/null +++ b/content/tags/privacy/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: Privacy +--- +summary: Data minimization, local ownership, credential handling, and private operation. diff --git a/content/tags/prompts/contents.lr b/content/tags/prompts/contents.lr new file mode 100644 index 0000000..387b0f5 --- /dev/null +++ b/content/tags/prompts/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: Prompts +--- +summary: Model prompt design, configuration, evaluation, and user control. diff --git a/content/tags/prototype/contents.lr b/content/tags/prototype/contents.lr new file mode 100644 index 0000000..9b79788 --- /dev/null +++ b/content/tags/prototype/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: Prototype +--- +summary: Early implementations used to test workflows, boundaries, and ideas. diff --git a/content/tags/release/contents.lr b/content/tags/release/contents.lr new file mode 100644 index 0000000..8f2f7c8 --- /dev/null +++ b/content/tags/release/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: Release +--- +summary: Preparing, packaging, publishing, and documenting software releases. diff --git a/content/tags/user-control/contents.lr b/content/tags/user-control/contents.lr new file mode 100644 index 0000000..b4f7794 --- /dev/null +++ b/content/tags/user-control/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: User Control +--- +summary: Explicit user authority, review, consent, and reversible operations. diff --git a/content/tags/v0-4-0/contents.lr b/content/tags/v0-4-0/contents.lr new file mode 100644 index 0000000..6b8d4be --- /dev/null +++ b/content/tags/v0-4-0/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: v0.4.0 +--- +summary: Work associated with the version 0.4.0 contract and milestone. diff --git a/content/tags/verification/contents.lr b/content/tags/verification/contents.lr new file mode 100644 index 0000000..1f57bfd --- /dev/null +++ b/content/tags/verification/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: Verification +--- +summary: Tests, assertions, conformance checks, and reproducible evidence. diff --git a/content/tags/writing-workspace/contents.lr b/content/tags/writing-workspace/contents.lr new file mode 100644 index 0000000..b20b072 --- /dev/null +++ b/content/tags/writing-workspace/contents.lr @@ -0,0 +1,5 @@ +_model: tag +--- +title: Writing Workspace +--- +summary: Structured environments for drafting, revision, and creative work. diff --git a/models/devlog-entry.ini b/models/devlog-entry.ini index 9d9a013..6065827 100644 --- a/models/devlog-entry.ini +++ b/models/devlog-entry.ini @@ -32,6 +32,18 @@ type = strings label = Imported topics type = strings +[fields.topic_labels] +label = Topic display labels +type = strings + +[fields.topic_tag_slugs] +label = Approved topic tags +type = strings + +[fields.kicker] +label = Type +type = string + [fields.source_commit] label = Source commit type = string diff --git a/scripts/project_sources.py b/scripts/project_sources.py index 318030f..6366595 100644 --- a/scripts/project_sources.py +++ b/scripts/project_sources.py @@ -647,12 +647,24 @@ def materialize( if record.get("_model") == "devlog-entry": entry_commit = record["source_commit"] entry_meta = commit_metadata(Path(metadata["mirror_path"]), entry_commit) + topic_values = split_list(record["tags"]) + topic_tag_slugs = approved_tag_slugs( + source["project_id"], + "devlog topic", + topic_values, + tag_vocabulary, + ) output = append_fields( data, { + "kicker": "Devlog", "source_commit_url": f"{source['web_url']}/commit/{entry_commit}", "source_commit_time": entry_meta["commit_date"], - "topic_tags": split_list(record["tags"]), + "topic_tags": topic_values, + "topic_labels": taxonomy_label_items( + topic_values, topic_tag_slugs + ), + "topic_tag_slugs": topic_tag_slugs, }, ) target.write_bytes(output) diff --git a/templates/devlog-entry.html b/templates/devlog-entry.html index 0419dbe..e113707 100644 --- a/templates/devlog-entry.html +++ b/templates/devlog-entry.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% from "macros/tag-links.html" import taxonomy_labels with context %} {% block body %} {% set project = this.parent.parent %} {% set siblings = this.get_siblings() %} @@ -19,7 +20,7 @@
Source commit
{{ this.source_commit[:10] }}
- {% if this.topic_tags %}
Topics
{% for tag in this.topic_tags %}{{ tag }}{% endfor %}
{% endif %} + {% if this.topic_labels %}
Topics
{{ taxonomy_labels(this.topic_labels) }}
{% endif %} diff --git a/templates/devlog.html b/templates/devlog.html index aa8d54b..ec61db7 100644 --- a/templates/devlog.html +++ b/templates/devlog.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% from "macros/tag-links.html" import taxonomy_labels with context %} {% block body %} {% set project = this.parent %}
@@ -17,7 +18,7 @@

{{ entry.title }}

{{ entry.summary }}

- {% if entry.topic_tags %}
{% for tag in entry.topic_tags %}{{ tag }}{% endfor %}
{% endif %} + {% if entry.topic_labels %}
{{ taxonomy_labels(entry.topic_labels) }}
{% endif %} Read entry → {% endfor %} diff --git a/templates/home.html b/templates/home.html index 0b45768..b4fb281 100644 --- a/templates/home.html +++ b/templates/home.html @@ -19,6 +19,16 @@ {% if entries.append(item) %}{% endif %} {% endfor %} {% endfor %} + {% for project in site.query('/projects') %} + {% if project._model == 'project' %} + {% set devlog = site.get(project.path ~ '/devlog') %} + {% if devlog %} + {% for entry in devlog.children %} + {% if entries.append(entry) %}{% endif %} + {% endfor %} + {% endif %} + {% endif %} + {% endfor %} {% for item in entries|sort(attribute='date', reverse=true) %}
- diff --git a/templates/tag-index.html b/templates/tag-index.html index e41529e..0051b96 100644 --- a/templates/tag-index.html +++ b/templates/tag-index.html @@ -18,6 +18,16 @@ {% if tag._id in item_tags %}{% set usage.count = usage.count + 1 %}{% endif %} {% endfor %} {% endfor %} + {% for project in site.query('/projects') %} + {% if project._model == 'project' %} + {% set devlog = site.get(project.path ~ '/devlog') %} + {% if devlog %} + {% for entry in devlog.children %} + {% if tag._id in entry.topic_tag_slugs %}{% set usage.count = usage.count + 1 %}{% endif %} + {% endfor %} + {% endif %} + {% endif %} + {% endfor %} {{ tag.title }} {{ usage.count }} {% if usage.count == 1 %}entry{% else %}entries{% endif %} diff --git a/templates/tag.html b/templates/tag.html index 868ae09..a87b1a8 100644 --- a/templates/tag.html +++ b/templates/tag.html @@ -8,6 +8,16 @@ {% if this._id in item_tags %}{% set matches.items = matches.items + [item] %}{% endif %} {% endfor %} {% endfor %} +{% for project in site.query('/projects') %} + {% if project._model == 'project' %} + {% set devlog = site.get(project.path ~ '/devlog') %} + {% if devlog %} + {% for entry in devlog.children %} + {% if this._id in entry.topic_tag_slugs %}{% set matches.items = matches.items + [entry] %}{% endif %} + {% endfor %} + {% endif %} + {% endif %} +{% endfor %}
diff --git a/tests/test_project_sources.py b/tests/test_project_sources.py index 6efc1f5..23e2c7d 100644 --- a/tests/test_project_sources.py +++ b/tests/test_project_sources.py @@ -15,6 +15,7 @@ from project_sources import ( # noqa: E402 append_fields, fetch_json, metadata_digest, + normalize_tag_value, parse_record, split_list, taxonomy_label_items, @@ -68,6 +69,10 @@ class ProjectSourceTests(unittest.TestCase): ) self.assertEqual(items, ["typescript\tTypeScript", "-\tUnlisted Tool"]) + def test_devlog_topic_versions_get_stable_tag_slugs(self) -> None: + self.assertEqual(normalize_tag_value("v0.4.0"), "v0-4-0") + self.assertEqual(normalize_tag_value("OpenAI Build Week"), "openai-build-week") + if __name__ == "__main__": unittest.main()