Skip to main content

max / makenotwork

Retire the What's new modal; state annotation reading in the privacy copy The footer already links /changelog, so the modal was a second door to one room. The auto-show-once interruption on a feature bump was judged not worth the machinery. The data-new-until badge mechanism goes with it: half of whats_new.js, and zero uses across templates since it was written. Separately, the privacy policy now says plainly that personal annotations are private to the user, that no other user or creator sees them, and that the server reads them in order to render them.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01DwpiantpUgohzML4xr6KeQ
Author: Max Johnson <me@maxj.phd> · 2026-08-30 17:14 UTC
Signed with PGP, not checked
Commit: b767fd52c80786e5207594524a73547ce636e907
Parent: 7e63515
5 files changed, +3 insertions, -191 deletions
@@ -157,7 +157,6 @@
157 157 "<script src=\"/static/actions-dashboards.js?v=0701\"></script>",
158 158 "<script src=\"/static/synckit-billing.js?v=0620\"></script>",
159 159 "<script src=\"/static/synckit-tabs.js?v=0623\"></script>",
160 - "<script src=\"/static/whats_new.js?v=0522\"></script>",
161 160 "<script src=\"/static/markdown-editor.js?v=0820\"></script>",
162 161 )
163 162 }
@@ -5172,29 +5172,6 @@
5172 5172 }
5173 5173 }
5174 5174
5175 - /* ===========================================
5176 - "NEW" BADGE (data-new-until)
5177 - ===========================================
5178 - Applied by whats_new.js while today's date is <= the element's
5179 - data-new-until. Renders a small violet dot to the right of the
5180 - element's content. Pair with relative-positioned parents. */
5181 -
5182 - .is-new {
5183 - position: relative;
5184 - }
5185 -
5186 - .is-new::after {
5187 - content: "";
5188 - position: absolute;
5189 - top: -2px;
5190 - right: -8px;
5191 - width: 6px;
5192 - height: 6px;
5193 - border-radius: var(--radius-round);
5194 - background: var(--action);
5195 - box-shadow: 0 0 0 2px var(--surface-page);
5196 - }
5197 -
5198 5175 /* ===========================================
5199 5176 RESTART WARNING BANNER
5200 5177 =========================================== */
@@ -46,7 +46,6 @@
46 46 {% if crate::changelog::is_published() %}<a href="/changelog">Changelog</a>{% endif %}
47 47 <a href="mailto:info@makenot.work">Contact</a>
48 48 <a href="/health" title="Service status and uptime">Status</a>
49 - <a href="#" data-prevent data-action="showWhatsNewModal">What's new</a>
50 49 <a href="#" data-prevent data-action="toggleShortcutsHelp" title="Keyboard shortcuts (?)">Shortcuts</a>
51 50 </div>
52 51 <p>&copy; 2026 Make Creative, LLC</p>
@@ -25,6 +25,9 @@
25 25 - Metadata you provide (titles, descriptions, tags)
26 26 - Projects and organization
27 27
28 + ### Personal Annotations
29 + Notes you attach to commits are private to you. No other user sees them, and neither does the creator whose repository you annotated. Our server reads them in order to render them back to you on the commit view, the same way it reads any private repository it holds. That means anyone with administrative access to our database could read them as well. We do not, outside of what running the service requires, and you should know the capability exists before writing something there you would not want held on our hardware.
30 +
28 31 ### Transactions
29 32 - Purchases and memberships
30 33 - Payment amounts and dates
@@ -1,166 +1,0 @@
1 - /* What's New modal + "New" badge mechanism.
2 - *
3 - * Two related UI surfaces that share localStorage as their source of truth:
4 - *
5 - * 1. Auto-show modal on a "feature version" bump. FEATURE_VERSION is an
6 - * opaque string controlled here (not CARGO_PKG_VERSION). Edit it when
7 - * you want to fire the modal, typically at the end of a sprint when
8 - * the changelog has something worth surfacing. Skipping a bump is
9 - * fine: nothing happens if FEATURE_VERSION matches the last-seen
10 - * version in localStorage.
11 - *
12 - * 2. "New" badge on individual links/buttons. Mark any element with
13 - * data-new-until="YYYY-MM-DD", JS adds a `is-new` class while today
14 - * is before the date. CSS renders the dot. Once the date passes the
15 - * class is removed at page load and the dot disappears.
16 - *
17 - * Both deliberately avoid server cooperation: no API to call, no template
18 - * plumbing, no version-detection brittleness. The whole feature lives in
19 - * this file plus the CSS for `.is-new`.
20 - */
21 -
22 - (function () {
23 - 'use strict';
24 -
25 - // ── What's New modal ──────────────────────────────────────────────
26 -
27 - /// Bump this when /changelog has shipped something users should see.
28 - /// Setting it to a NEW value triggers the modal once per user.
29 - var FEATURE_VERSION = 'v0.8-synckit-per-key';
30 -
31 - /// Summary shown in the modal body. Keep to 1–3 sentences; the link
32 - /// to /changelog is the canonical full list.
33 - var FEATURE_HEADLINE = 'SyncKit per-key storage';
34 - var FEATURE_BODY =
35 - "SyncKit apps now bill per developer-defined key rather than per app, " +
36 - "with mini-gauges in the dashboard and per-key warning emails. " +
37 - "Existing SyncKit clients keep working; the SDK signature is updated " +
38 - "for new integrations.";
39 -
40 - var STORAGE_KEY = 'mnw_seen_feature_version';
41 -
42 - function safeGet(key) {
43 - try { return localStorage.getItem(key); } catch (e) { return null; }
44 - }
45 - function safeSet(key, value) {
46 - try { localStorage.setItem(key, value); } catch (e) { /* ignore */ }
47 - }
48 -
49 - /// Show the modal regardless of seen state. Used by the "What's new"
50 - /// link in the footer.
51 - function showWhatsNewModal() {
52 - var existing = document.getElementById('whats-new-modal');
53 - if (existing) { existing.remove(); return; }
54 -
55 - var overlay = document.createElement('div');
56 - overlay.id = 'whats-new-modal';
57 - overlay.className = 'modal-overlay';
58 - overlay.style.display = 'flex';
59 - overlay.onclick = function (e) {
60 - if (e.target === overlay) {
61 - overlay.remove();
62 - safeSet(STORAGE_KEY, FEATURE_VERSION);
63 - }
64 - };
65 -
66 - var content = document.createElement('div');
67 - content.className = 'modal-content';
68 - content.style.maxWidth = '480px';
69 - content.style.padding = '2rem';
70 -
71 - var header = document.createElement('div');
72 - header.className = 'modal-header';
73 - header.style.marginBottom = '1rem';
74 - var h2 = document.createElement('h2');
75 - h2.textContent = "What's new: " + FEATURE_HEADLINE;
76 - header.appendChild(h2);
77 - var closeBtn = document.createElement('button');
78 - closeBtn.type = 'button';
79 - closeBtn.className = 'modal-close';
80 - closeBtn.setAttribute('aria-label', 'Dismiss');
81 - closeBtn.innerHTML = '&times;';
82 - closeBtn.onclick = function () {
83 - overlay.remove();
84 - safeSet(STORAGE_KEY, FEATURE_VERSION);
85 - };
86 - header.appendChild(closeBtn);
87 -
88 - var body = document.createElement('p');
89 - body.textContent = FEATURE_BODY;
90 -
91 - // /changelog 404s until a changelog project is published, and base.html
92 - // only renders the footer link while it is. Reuse that server-side guard
93 - // rather than duplicating the check here.
94 - var link = null;
95 - if (document.querySelector('.site-footer-links a[href="/changelog"]')) {
96 - link = document.createElement('a');
97 - link.href = '/changelog';
98 - link.textContent = 'Full changelog →';
99 - link.className = 'section-link';
100 - link.style.display = 'inline-block';
101 - link.style.marginTop = '0.75rem';
102 - }
103 -
104 - content.appendChild(header);
105 - content.appendChild(body);
106 - if (link) {
107 - content.appendChild(link);
108 - }
109 - overlay.appendChild(content);
110 - document.body.appendChild(overlay);
111 - }
112 -
113 - /// Auto-show on first visit after a feature-version bump. Records the
114 - /// seen version in localStorage so dismissal sticks across reloads.
115 - function maybeAutoShowWhatsNew() {
116 - var seen = safeGet(STORAGE_KEY);
117 - if (seen === FEATURE_VERSION) return;
118 - // First-ever visit also seeds the storage, no modal flash for
119 - // genuinely new users (they're already getting onboarded).
120 - if (seen === null) {
121 - safeSet(STORAGE_KEY, FEATURE_VERSION);
122 - return;
123 - }
124 - showWhatsNewModal();
125 - }
126 -
127 - // Expose for footer link click + onboarding flows.
128 - window.showWhatsNewModal = showWhatsNewModal;
129 -
130 - // ── "New" badge ────────────────────────────────────────────────────
131 -
132 - /// Walk every `[data-new-until]` element. If today < that date, mark
133 - /// it `.is-new` so the CSS dot renders. Otherwise strip the attribute
134 - /// so it doesn't get re-evaluated on later page loads.
135 - function applyNewBadges() {
136 - var today = new Date();
137 - today.setHours(0, 0, 0, 0);
138 - var nodes = document.querySelectorAll('[data-new-until]');
139 - for (var i = 0; i < nodes.length; i++) {
140 - var el = nodes[i];
141 - var until = new Date(el.getAttribute('data-new-until'));
142 - if (isNaN(until.getTime())) {
143 - el.removeAttribute('data-new-until');
144 - continue;
145 - }
146 - if (today <= until) {
147 - el.classList.add('is-new');
148 - } else {
149 - el.removeAttribute('data-new-until');
150 - el.classList.remove('is-new');
151 - }
152 - }
153 - }
154 -
155 - // ── Init ──────────────────────────────────────────────────────────
156 -
157 - if (document.readyState === 'loading') {
158 - document.addEventListener('DOMContentLoaded', function () {
159 - applyNewBadges();
160 - maybeAutoShowWhatsNew();
161 - });
162 - } else {
163 - applyNewBadges();
164 - maybeAutoShowWhatsNew();
165 - }
166 - })();