From 3179f832fc50627b1fd62bbe0ef41a44c0c0f311 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 10 Jun 2026 16:01:18 +0100 Subject: [PATCH 01/10] Refine day browser selection UI --- artifacts/views.py | 1 + static/daybook.css | 27 +++++++++++++++++++++++++++ templates/artifacts/feed.html | 4 ++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/artifacts/views.py b/artifacts/views.py index 7c2b847..5a33a9d 100644 --- a/artifacts/views.py +++ b/artifacts/views.py @@ -116,6 +116,7 @@ def feed(request): "selected_kind": selected_kind, "selected_persona": persona, "selected_view": selected_view, + "today": timezone.localdate(), "title_query": title_query, "kind_choices": Artifact.KIND_CHOICES, }, diff --git a/static/daybook.css b/static/daybook.css index f0ed612..c142547 100644 --- a/static/daybook.css +++ b/static/daybook.css @@ -207,6 +207,21 @@ h1 { box-shadow: 0 10px 24px rgba(57, 44, 28, 0.08); } +.day-link.selected { + background: #17313b; + color: #fffaf0; +} + +.day-link.selected span, +.day-link.selected strong { + color: #fffaf0; +} + +.day-link.selected .today-badge { + background: #b9e8bf; + color: #17313b; +} + .day-link span, .pane-heading span, .artifact-row-main strong { @@ -214,6 +229,18 @@ h1 { font-size: 0.7rem; } +.today-badge { + background: #cfe9cf; + border-radius: 999px; + color: #245238; + display: inline-block; + font-size: 0.62rem; + font-style: normal; + font-weight: 500; + margin-left: 0.35rem; + padding: 0.08rem 0.38rem; +} + .day-link strong { color: #17313b; font-size: 0.82rem; diff --git a/templates/artifacts/feed.html b/templates/artifacts/feed.html index 538c6be..83626da 100644 --- a/templates/artifacts/feed.html +++ b/templates/artifacts/feed.html @@ -50,7 +50,7 @@ + +
{% if selected_artifact %}
@@ -218,6 +221,35 @@ copyButton.disabled = false; copyButton.dataset.copyUrl = row.dataset.renderedUrl; } + syncKeepButton(row.dataset.kept === "1"); + const keepButton = actions.querySelector("[data-keep-button]"); + if (keepButton) keepButton.dataset.artifactId = row.dataset.artifactId; + actions.querySelector("[data-details-toggle]").disabled = false; + renderDetails(row); + }; + + const syncKeepButton = (isKept) => { + const keepButton = document.querySelector("[data-keep-button]"); + if (!keepButton) return; + keepButton.disabled = false; + keepButton.classList.toggle("kept", isKept); + keepButton.textContent = isKept ? "✓ Kept" : "Keep"; + }; + + const renderDetails = (row) => { + const panel = document.querySelector("[data-details-panel]"); + if (!panel) return; + const d = row.dataset; + const tags = d.tags ? d.tags.split(",").map((t) => t.trim()).filter(Boolean) : []; + const rows = [ + ["Source", `${escapeHtml(d.source)} (${escapeHtml(d.sourceType)})`], + ["Source name", escapeHtml(d.sourceName || "—")], + ["Type", escapeHtml(d.kind)], + ["Created", escapeHtml(d.created)], + ["Size", escapeHtml(d.size)], + ["Tags", tags.length ? tags.map(escapeHtml).join(", ") : "—"], + ]; + panel.innerHTML = `
${rows.map(([k, v]) => `
${k}
${v}
`).join("")}
`; }; const disableActions = () => { @@ -240,6 +272,15 @@ copyButton.disabled = true; copyButton.dataset.copyUrl = ""; } + const keepButton = actions.querySelector("[data-keep-button]"); + if (keepButton) { + keepButton.disabled = true; + keepButton.classList.remove("kept"); + keepButton.textContent = "Keep"; + } + actions.querySelector("[data-details-toggle]").disabled = true; + const panel = document.querySelector("[data-details-panel]"); + if (panel) panel.hidden = true; }; const markRead = async (row) => { @@ -440,6 +481,34 @@ observeSentinel(); + // Populate the details panel for a server-preselected artifact so the first + // "Details" click shows content immediately. + const preselected = selectedRow(); + if (preselected) renderDetails(preselected); + + const toggleKeep = async (button) => { + const artifactId = button.dataset.artifactId; + if (!artifactId) return; + button.disabled = true; + try { + const response = await fetch(`/a/${artifactId}/keep`, { + method: "POST", + headers: {"X-CSRFToken": csrfToken}, + }); + const data = await response.json(); + syncKeepButton(data.is_kept); + const row = document.querySelector(`.artifact-row[data-artifact-id="${artifactId}"]`); + if (row) { + row.dataset.kept = data.is_kept ? "1" : "0"; + row.classList.toggle("kept", data.is_kept); + const badge = row.querySelector("[data-kept-badge]"); + if (badge) badge.hidden = !data.is_kept; + } + } catch { + button.disabled = false; + } + }; + previewPane.addEventListener("click", (event) => { const copyButton = event.target.closest("[data-copy-url]"); if (copyButton) { @@ -448,6 +517,24 @@ return; } + const keepButton = event.target.closest("[data-keep-button]"); + if (keepButton && !keepButton.disabled) { + event.preventDefault(); + toggleKeep(keepButton); + return; + } + + const detailsToggle = event.target.closest("[data-details-toggle]"); + if (detailsToggle && !detailsToggle.disabled) { + event.preventDefault(); + const panel = document.querySelector("[data-details-panel]"); + if (panel) { + panel.hidden = !panel.hidden; + detailsToggle.classList.toggle("selected", !panel.hidden); + } + return; + } + const toggle = event.target.closest("[data-view]"); if (!toggle) return; if (toggle.classList.contains("disabled")) { diff --git a/templates/artifacts/settings_base.html b/templates/artifacts/settings_base.html index f6df38f..3b205b6 100644 --- a/templates/artifacts/settings_base.html +++ b/templates/artifacts/settings_base.html @@ -13,6 +13,7 @@
diff --git a/templates/artifacts/settings_storage.html b/templates/artifacts/settings_storage.html new file mode 100644 index 0000000..b511fa0 --- /dev/null +++ b/templates/artifacts/settings_storage.html @@ -0,0 +1,39 @@ +{% extends "artifacts/settings_base.html" %} + +{% block settings_content %} +

Storage

+ +
+
+
+

+ {{ used_bytes|filesizeformat }} of {{ quota_bytes|filesizeformat }} used ({{ percent_used }}%) +

+ +
    +
  • {{ artifact_count }} artifact{{ artifact_count|pluralize }} total
  • +
  • {{ kept_count }} kept ({{ kept_bytes|filesizeformat }}) — never auto-deleted
  • +
+ +

+ When your quota fills, the oldest unkept artifacts are deleted + automatically to make room for new ones. Click Keep on an artifact + to protect it. If the quota is full of kept artifacts, new posts are rejected until + you free space. +

+ + {% if is_superuser %} +

Admin

+
    +
  • Total quota allocated across all users: {{ total_allocated|filesizeformat }}
  • +
  • Free space on the artifact disk: {{ disk_free|filesizeformat }}
  • +
+ {% if total_allocated > disk_free %} +

+ ⚠ Allocated quota exceeds free disk space. Grow the volume before raising quotas, + or usage could fill the actual disk. +

+ {% endif %} +

Per-user quotas are set in the Django admin.

+ {% endif %} +{% endblock %} From 13f728bcd6fcf97d228a1265953524a0ba6f77e6 Mon Sep 17 00:00:00 2001 From: ritte Date: Fri, 24 Jul 2026 10:28:12 +0200 Subject: [PATCH 08/10] Add title tooltip and widen artifact-list resize range - Preview control-bar title now shows a native tooltip with the full text on hover, so titles truncated by ellipsis are still readable. Wired into the server render and both JS update paths. - Splitter resize bounds are now per-pane: the artifact list can grow up to 720px (was a shared 420px cap that made it feel un-draggable to the right), while the sources sidebar stays 96-320px. Co-Authored-By: Claude Opus 4.8 --- templates/artifacts/feed.html | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/templates/artifacts/feed.html b/templates/artifacts/feed.html index fc6d806..447fec0 100644 --- a/templates/artifacts/feed.html +++ b/templates/artifacts/feed.html @@ -59,7 +59,7 @@
-
+
{% if selected_artifact %}{{ selected_artifact.title }}{% else %}No artifact loaded{% endif %}