v0.6.3: UI wording -> "messages"; folder spacing + tint

- User-facing "artifact(s)" -> "messages" across the UI (feed count, empty
  states, preview, About, Storage). Model/API/skill keep "artifact" internally
  — no agent-contract change.
- Sidebar: Keep/Deleted folders get spacing between them and share the "All
  sources" bold + blue-tint styling (special folders).
- Fix: About page points to Settings -> API Keys (was stale "Settings ->
  Account / API token").

152 tests pass; inline JS re-validated with node --check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
peter 2026-09-07 21:37:08 +01:00
parent c1be2face7
commit 7b161f1276
7 changed files with 52 additions and 32 deletions

View file

@ -73,7 +73,7 @@
{% if selected_folder == 'deleted' %}
<button type="button" class="empty-trash-button" data-empty-trash>Empty Trash</button>
{% else %}
<span>{{ artifacts|length }} artifact{{ artifacts|length|pluralize }}</span>
<span>{{ artifacts|length }} message{{ artifacts|length|pluralize }}</span>
{% endif %}
</div>
@ -82,7 +82,7 @@
{% include "artifacts/_day_groups.html" with has_more=older_count next_before=window_start|date:"Y-m-d" %}
{% else %}
<div class="empty compact">
<h3>No artifacts</h3>
<h3>No messages</h3>
<p>Nothing in the last {{ 7 }} days for these filters.</p>
</div>
{% endif %}
@ -94,7 +94,7 @@
<section class="preview-pane">
<header class="pane-bar preview-actions" data-actions>
<div class="preview-title" data-preview-title {% if selected_artifact %}title="{{ selected_artifact.title }}"{% endif %}>
{% if selected_artifact %}{{ selected_artifact.title }}{% else %}No artifact loaded{% endif %}
{% if selected_artifact %}{{ selected_artifact.title }}{% else %}No message loaded{% endif %}
</div>
<nav class="view-toggle" aria-label="Preview mode">
<a class="{% if selected_view == 'rendered' %}selected{% endif %}{% if not selected_artifact %} disabled{% endif %}" href="{% if selected_artifact %}{{ rendered_url }}{% else %}#{% endif %}" data-view="rendered" {% if not selected_artifact %}aria-disabled="true"{% endif %}>Rendered</a>
@ -124,8 +124,8 @@
<img class="preview-image" src="{% url 'artifact' selected_artifact.id %}" alt="{{ selected_artifact.title }}">
{% elif selected_artifact.kind == "binary" %}
<div class="empty preview-empty">
<h2>Binary Artifact</h2>
<p>This artifact type cannot be previewed inline yet.</p>
<h2>Binary Message</h2>
<p>This message type cannot be previewed inline yet.</p>
<p><a href="{% url 'artifact' selected_artifact.id %}">Open or download it</a>.</p>
</div>
{% else %}
@ -135,10 +135,10 @@
{% else %}
<div class="empty preview-empty">
{% if artifacts %}
<h2>No artifact loaded</h2>
<p>Press <kbd>Right Arrow</kbd> or select an artifact from the middle column to preview it.</p>
<h2>No message loaded</h2>
<p>Press <kbd>Right Arrow</kbd> or select a message from the middle column to preview it.</p>
{% else %}
<h2>No artifacts for this day</h2>
<h2>No messages for this day</h2>
<p>This day is ready for new agent output.</p>
{% endif %}
</div>
@ -239,7 +239,7 @@
try {
await navigator.clipboard.writeText(absoluteUrl);
} catch {
window.prompt("Copy artifact link", absoluteUrl);
window.prompt("Copy message link", absoluteUrl);
}
const original = button.textContent;
button.textContent = "Copied";
@ -348,7 +348,7 @@
if (!actions) return;
const previewTitle = actions.querySelector("[data-preview-title]");
if (previewTitle) {
previewTitle.textContent = "No artifact loaded";
previewTitle.textContent = "No message loaded";
previewTitle.removeAttribute("title");
}
actions.querySelectorAll("[data-view]").forEach((link) => {
@ -484,7 +484,7 @@
const header = document.querySelector(".artifact-sidebar .pane-heading span");
if (!header) return;
const total = Math.max(0, (parseInt(header.textContent, 10) || 0) + delta);
header.textContent = `${total} artifact${total === 1 ? "" : "s"}`;
header.textContent = `${total} message${total === 1 ? "" : "s"}`;
};
const bumpFolderCount = (folder, delta) => {
@ -632,7 +632,7 @@
: kind === "image"
? `<img class="preview-image" src="${source}" alt="${title}">`
: kind === "binary"
? `<div class="empty preview-empty"><h2>Binary Artifact</h2><p>This artifact type cannot be previewed inline yet.</p><p><a href="${source}">Open or download it</a>.</p></div>`
? `<div class="empty preview-empty"><h2>Binary Message</h2><p>This message type cannot be previewed inline yet.</p><p><a href="${source}">Open or download it</a>.</p></div>`
: `<iframe class="preview-frame" src="${source}" title="${title}"></iframe>`;
previewBody.innerHTML = `<div class="preview-frame-wrap">${content}</div>`;
@ -647,7 +647,7 @@
}
allRows().forEach((candidate) => candidate.classList.remove("selected"));
disableActions();
previewBody.innerHTML = `<div class="empty preview-empty"><h2>No artifact loaded</h2><p>Press <kbd>Right Arrow</kbd> or select an artifact from the middle column to preview it.</p></div>`;
previewBody.innerHTML = `<div class="empty preview-empty"><h2>No message loaded</h2><p>Press <kbd>Right Arrow</kbd> or select a message from the middle column to preview it.</p></div>`;
window.history.pushState({}, "", noPreviewUrl());
const currentSource = document.querySelector(".source-link.selected");
if (currentSource) currentSource.focus({preventScroll: true});
@ -885,7 +885,7 @@
const header = document.querySelector(".artifact-sidebar .pane-heading span");
if (header) {
const total = (parseInt(header.textContent, 10) || 0) + data.count;
header.textContent = `${total} artifact${total === 1 ? "" : "s"}`;
header.textContent = `${total} message${total === 1 ? "" : "s"}`;
}
} catch {
// ignore; retry next tick

View file

@ -10,11 +10,11 @@
</div>
<p>A self-hosted, multi-user journal where AI agents, scripts, or people
publish artifacts — reports, HTML, decks, images, PDFs, JSON, CSV — and each
publish messages — reports, HTML, decks, images, PDFs, JSON, CSV — and each
logged-in user browses their own time-ordered, label-filterable feed. The
server owns IDs, styling, and storage; agents publish content, not files.</p>
<p class="storage-note">Post to it with a personal API token from
Settings&nbsp;→&nbsp;Account; the current API capabilities (including this
version) are available at <code>/api/capabilities</code>.</p>
<p class="storage-note">Post to it with a personal API key from
Settings&nbsp;→&nbsp;API&nbsp;Keys; the current API capabilities (including
this version) are available at <code>/api/capabilities</code>.</p>
{% endblock %}

View file

@ -2,7 +2,7 @@
{% block settings_content %}
<h2>Demo Data</h2>
<p>Generate one sample artifact of every supported kind (markdown, HTML fragment, full HTML, deck, image, PDF, binary), tagged "demo" and dated today.</p>
<p>Generate one sample message of every supported kind (markdown, HTML fragment, full HTML, deck, image, PDF, binary), tagged "demo" and dated today.</p>
<form method="post">
{% csrf_token %}
<button type="submit">Generate Demo Data</button>

View file

@ -11,14 +11,14 @@
</p>
<ul class="storage-facts">
<li>{{ artifact_count }} artifact{{ artifact_count|pluralize }} total</li>
<li>{{ artifact_count }} message{{ artifact_count|pluralize }} total</li>
<li>{{ kept_count }} kept ({{ kept_bytes|filesizeformat }}) &mdash; never auto-deleted</li>
</ul>
<p class="storage-note">
When your quota fills, the oldest <strong>unkept</strong> artifacts are deleted
automatically to make room for new ones. Click <strong>Keep</strong> on an artifact
to protect it. If the quota is full of kept artifacts, new posts are rejected until
When your quota fills, the oldest <strong>unkept</strong> messages are deleted
automatically to make room for new ones. Click <strong>Keep</strong> on a message
to protect it. If the quota is full of kept messages, new posts are rejected until
you free space.
</p>
@ -26,7 +26,7 @@
<h3>Admin</h3>
<ul class="storage-facts">
<li>Total quota allocated across all users: <strong>{{ total_allocated|filesizeformat }}</strong></li>
<li>Free space on the artifact disk: <strong>{{ disk_free|filesizeformat }}</strong></li>
<li>Free space on the storage disk: <strong>{{ disk_free|filesizeformat }}</strong></li>
</ul>
{% if total_allocated > disk_free %}
<p class="storage-warning">