This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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/<entry-slug>/`. 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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: Architecture
|
||||
---
|
||||
summary: Product boundaries, system structure, and consequential technical design.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: Configuration
|
||||
---
|
||||
summary: User-editable and system configuration design and implementation.
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: Documentation
|
||||
---
|
||||
summary: Technical, operational, product, and user-facing documentation.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: Interface
|
||||
---
|
||||
summary: Interaction models, workspace layout, and user-interface design.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: JSON Schema
|
||||
---
|
||||
summary: Machine-readable JSON contracts, validation, fixtures, and compatibility.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: Models
|
||||
---
|
||||
summary: AI model integration, selection, configuration, and behavior.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: MVP
|
||||
---
|
||||
summary: Minimum viable product milestones and end-to-end prototypes.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: OpenAI Build Week
|
||||
---
|
||||
summary: Projects, milestones, and releases developed during OpenAI Build Week.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: Privacy
|
||||
---
|
||||
summary: Data minimization, local ownership, credential handling, and private operation.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: Prompts
|
||||
---
|
||||
summary: Model prompt design, configuration, evaluation, and user control.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: Prototype
|
||||
---
|
||||
summary: Early implementations used to test workflows, boundaries, and ideas.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: Release
|
||||
---
|
||||
summary: Preparing, packaging, publishing, and documenting software releases.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: User Control
|
||||
---
|
||||
summary: Explicit user authority, review, consent, and reversible operations.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: v0.4.0
|
||||
---
|
||||
summary: Work associated with the version 0.4.0 contract and milestone.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: Verification
|
||||
---
|
||||
summary: Tests, assertions, conformance checks, and reproducible evidence.
|
||||
@@ -0,0 +1,5 @@
|
||||
_model: tag
|
||||
---
|
||||
title: Writing Workspace
|
||||
---
|
||||
summary: Structured environments for drafting, revision, and creative work.
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 @@
|
||||
<dt>Source commit</dt>
|
||||
<dd><a href="{{ this.source_commit_url }}"><code>{{ this.source_commit[:10] }}</code></a></dd>
|
||||
</div>
|
||||
{% if this.topic_tags %}<div class="post-info-row"><dt>Topics</dt><dd class="technology-list">{% for tag in this.topic_tags %}<span>{{ tag }}</span>{% endfor %}</dd></div>{% endif %}
|
||||
{% if this.topic_labels %}<div class="post-info-row"><dt>Topics</dt><dd class="technology-list">{{ taxonomy_labels(this.topic_labels) }}</dd></div>{% endif %}
|
||||
</dl>
|
||||
</aside>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "macros/tag-links.html" import taxonomy_labels with context %}
|
||||
{% block body %}
|
||||
{% set project = this.parent %}
|
||||
<section class="hero compact devlog-hero">
|
||||
@@ -17,7 +18,7 @@
|
||||
<div class="entry-meta"><span class="label">Devlog</span><time datetime="{{ entry.date }}">{{ entry.date|dateformat('YYYY.MM.dd') }}</time></div>
|
||||
<h2><a href="{{ entry|url }}">{{ entry.title }}</a></h2>
|
||||
<p>{{ entry.summary }}</p>
|
||||
{% if entry.topic_tags %}<div class="technology-list">{% for tag in entry.topic_tags %}<span>{{ tag }}</span>{% endfor %}</div>{% endif %}
|
||||
{% if entry.topic_labels %}<div class="technology-list">{{ taxonomy_labels(entry.topic_labels) }}</div>{% endif %}
|
||||
<a class="read-more" href="{{ entry|url }}">Read entry →</a>
|
||||
</article>
|
||||
{% endfor %}
|
||||
|
||||
+10
-1
@@ -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) %}
|
||||
<article class="timeline-entry accent-{{ item.kicker|lower }}">
|
||||
<div class="entry-meta">
|
||||
@@ -33,4 +43,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
<a class="tag-directory-card" href="{{ tag|url }}">
|
||||
<span class="tag-directory-name">{{ tag.title }}</span>
|
||||
<span class="tag-directory-count">{{ usage.count }} {% if usage.count == 1 %}entry{% else %}entries{% endif %}</span>
|
||||
|
||||
+12
-2
@@ -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 %}
|
||||
<section class="hero compact">
|
||||
<div class="shell narrow">
|
||||
<p class="eyebrow">Tag</p>
|
||||
@@ -24,7 +34,7 @@
|
||||
{% if matches.items %}
|
||||
<div class="card-grid">
|
||||
{% for item in matches.items|sort(attribute='date', reverse=true) %}
|
||||
{% set item_tags = item.taxonomy_tags if item._model == 'project' else item.tags %}
|
||||
{% set item_tags = item.taxonomy_tags if item._model == 'project' else (item.topic_tag_slugs if item._model == 'devlog-entry' else item.tags) %}
|
||||
<article class="entry-card accent-{{ item.kicker|lower }}">
|
||||
<div class="entry-meta"><span class="label">{{ item.kicker }}</span><time>{{ item.date|dateformat('YYYY.MM.dd') }}</time></div>
|
||||
<h2><a href="{{ item|url }}">{{ item.title }}</a></h2>
|
||||
@@ -35,7 +45,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="empty-state">No projects, blog entries, or articles use this tag yet.</p>
|
||||
<p class="empty-state">No projects, devlog entries, blog entries, or articles use this tag yet.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user