max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
6 files changed,
+76 insertions,
-27 deletions
| @@ -97,7 +97,7 @@ | |||
| 97 | 97 | }; | |
| 98 | 98 | ||
| 99 | 99 | window.copyKeyCode = function (code) { | |
| 100 | - | navigator.clipboard.writeText(code); this.textContent = 'Copied!'; setTimeout(() => this.textContent = code, 1500); | |
| 100 | + | window.copyWithFeedback(this, code, 'Copied!', 1500); | |
| 101 | 101 | }; | |
| 102 | 102 | ||
| 103 | 103 | window.toggleContextMenuBtn = function (itemId) { | |
| @@ -130,11 +130,14 @@ | |||
| 130 | 130 | }; | |
| 131 | 131 | ||
| 132 | 132 | window.copyElementText = function (id) { | |
| 133 | - | navigator.clipboard.writeText(document.getElementById(id).textContent).then(() => { this.textContent = 'Copied'; setTimeout(() => this.textContent = 'Copy', 1500); }); | |
| 133 | + | window.copyWithFeedback(this, document.getElementById(id).textContent, 'Copied', 1500); | |
| 134 | 134 | }; | |
| 135 | 135 | ||
| 136 | + | // 2000ms here rather than the 1500 every other copy button uses. Preserved as | |
| 137 | + | // found: which of the two is right is a question for the semantic-timing work, | |
| 138 | + | // not for a consolidation pass. | |
| 136 | 139 | window.copyFeedUrl = function () { | |
| 137 | - | navigator.clipboard.writeText(document.getElementById('feed-url').value).then(() => { this.textContent = 'Copied!'; setTimeout(() => this.textContent = 'Copy URL', 2000); }); | |
| 140 | + | window.copyWithFeedback(this, document.getElementById('feed-url').value, 'Copied!', 2000); | |
| 138 | 141 | }; | |
| 139 | 142 | ||
| 140 | 143 | // --- user_media.html --- |
| @@ -29,21 +29,13 @@ | |||
| 29 | 29 | btn.className = className; | |
| 30 | 30 | btn.textContent = 'Copy'; | |
| 31 | 31 | btn.addEventListener('click', function () { | |
| 32 | - | navigator.clipboard.writeText(text).then(function () { | |
| 33 | - | btn.textContent = 'Copied'; | |
| 34 | - | setTimeout(function () { btn.textContent = 'Copy'; }, 1500); | |
| 35 | - | }); | |
| 32 | + | window.copyWithFeedback(btn, text, 'Copied', 1500); | |
| 36 | 33 | }); | |
| 37 | 34 | return btn; | |
| 38 | 35 | } | |
| 39 | 36 | ||
| 40 | 37 | window.syncKitCopyKey = function (event, key) { | |
| 41 | - | navigator.clipboard.writeText(key).then(function () { | |
| 42 | - | var btn = event.target; | |
| 43 | - | var orig = btn.textContent; | |
| 44 | - | btn.textContent = 'Copied'; | |
| 45 | - | setTimeout(function () { btn.textContent = orig; }, 1500); | |
| 46 | - | }); | |
| 38 | + | window.copyWithFeedback(event.target, key, 'Copied', 1500); | |
| 47 | 39 | }; | |
| 48 | 40 | ||
| 49 | 41 | window.syncKitRegenKey = function (appId) { |
| @@ -5,11 +5,7 @@ | |||
| 5 | 5 | var itemTitle = cfg.dataset.itemTitle; | |
| 6 | 6 | ||
| 7 | 7 | window.copyEmbed = function(btn) { | |
| 8 | - | const code = btn.previousElementSibling.textContent; | |
| 9 | - | navigator.clipboard.writeText(code).then(() => { | |
| 10 | - | btn.textContent = 'Copied!'; | |
| 11 | - | setTimeout(() => btn.textContent = 'Copy', 1500); | |
| 12 | - | }); | |
| 8 | + | window.copyWithFeedback(btn, btn.previousElementSibling.textContent, 'Copied!', 1500); | |
| 13 | 9 | }; | |
| 14 | 10 | ||
| 15 | 11 | window.switchCardLayout = function(layout) { |
| @@ -7,6 +7,14 @@ | |||
| 7 | 7 | //! and that is currently the largest standing cost of the conversion, paid on | |
| 8 | 8 | //! every page load by every visitor including signed-out ones. | |
| 9 | 9 | //! | |
| 10 | + | //! # Parked, 2026-08-18 | |
| 11 | + | //! | |
| 12 | + | //! The ratchet below is `#[ignore]`d by Max's ruling until the conversion is | |
| 13 | + | //! finished and the duplicated CSS is gone. Everything this doc argues still | |
| 14 | + | //! holds and none of it is retracted; see the test's own doc comment for why the | |
| 15 | + | //! enforcement was suspended anyway, and for what turns it back on. | |
| 16 | + | //! `the_two_lists_agree` is unaffected and still runs. | |
| 17 | + | //! | |
| 10 | 18 | //! # Why this seal exists rather than a note saying the same thing | |
| 11 | 19 | //! | |
| 12 | 20 | //! The conversion's payoff is the hand-written sheet shrinking. Without a | |
| @@ -83,7 +91,42 @@ | |||
| 83 | 91 | (each.iter().map(|(_, bytes)| bytes).sum(), each) | |
| 84 | 92 | } | |
| 85 | 93 | ||
| 94 | + | /// Parked by Max on 2026-08-18, and the module doc above argues against parking | |
| 95 | + | /// it, so the argument gets answered rather than ignored. | |
| 96 | + | /// | |
| 97 | + | /// What the doc says: a guard people edit rather than read is not a guard, and | |
| 98 | + | /// without a number "we converted a screen" and "we removed its cost" are the | |
| 99 | + | /// same claim. Both points stand. What they do not survive is the assertion | |
| 100 | + | /// pair below: this test fails on any decrease as well as any increase, so mid | |
| 101 | + | /// conversion it fires on work going in the direction it exists to reward, and | |
| 102 | + | /// the number gets re-baselined every batch. A seal that is re-baselined on | |
| 103 | + | /// schedule measures nothing, and it teaches exactly the editing habit the doc | |
| 104 | + | /// warns about. | |
| 105 | + | /// | |
| 106 | + | /// So the number is kept and the enforcement is suspended, rather than the seal | |
| 107 | + | /// being quietly lowered batch by batch. `CSS_KIB_HIGH_WATER` stays at 337 | |
| 108 | + | /// because it is the record of where the conversion started, and it is what the | |
| 109 | + | /// finished conversion gets measured against. | |
| 110 | + | /// | |
| 111 | + | /// WHAT BRINGS IT BACK: the conversion complete and the duplicated CSS removed. | |
| 112 | + | /// At that point the number stops moving in both directions, and the decision | |
| 113 | + | /// deferred with it becomes worth taking: seal the minified size rather than raw | |
| 114 | + | /// bytes, which is what Max ruled earlier the same day. `lightningcss` is | |
| 115 | + | /// already a dependency of this repo, so that costs no new dependency. | |
| 116 | + | /// | |
| 117 | + | /// STATE AT PARKING, measured 2026-08-18: | |
| 118 | + | /// | |
| 119 | + | /// static/style.css 324,648 (322,102 at the 2026-08-15 baseline) | |
| 120 | + | /// static/wizard.css 13,459 | |
| 121 | + | /// static/media-player.css 8,953 | |
| 122 | + | /// static/no-js.css 1,212 | |
| 123 | + | /// total 348,272 = 340 KiB against the sealed 337 | |
| 124 | + | /// | |
| 125 | + | /// Red on the grow assertion by 3 KiB, which is what prompted the ruling. | |
| 126 | + | /// | |
| 127 | + | /// GoingsOn mnw-server `2d01900f`. | |
| 86 | 128 | #[test] | |
| 129 | + | #[ignore = "parked by Max 2026-08-18 until the conversion is finished and the duplicated CSS is gone: see this test's doc comment"] | |
| 87 | 130 | fn hand_written_css_does_not_grow() { | |
| 88 | 131 | let (total, each) = measure(); | |
| 89 | 132 | let kib = total / 1024; |
| @@ -4,7 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | 5 | import { resolveHtmxLoadingButton } from './loading.ts'; | |
| 6 | 6 | import { csrfHeaders } from './net.ts'; | |
| 7 | - | import { showToast } from './toast.ts'; | |
| 7 | + | import { autoDismiss, showToast } from './toast.ts'; | |
| 8 | 8 | ||
| 9 | 9 | interface HxDetail { | |
| 10 | 10 | elt: HTMLElement; | |
| @@ -78,10 +78,9 @@ | |||
| 78 | 78 | toast.appendChild(closeBtn); | |
| 79 | 79 | ||
| 80 | 80 | container.appendChild(toast); | |
| 81 | - | setTimeout(() => { | |
| 82 | - | toast.classList.add('fade-out'); | |
| 83 | - | setTimeout(() => toast.remove(), 300); | |
| 84 | - | }, 6000); | |
| 81 | + | // 6000 rather than the 3000 an ordinary toast gets: this one carries a Retry | |
| 82 | + | // button, so it has to outlive a glance. | |
| 83 | + | autoDismiss(toast, 6000); | |
| 85 | 84 | }); | |
| 86 | 85 | ||
| 87 | 86 | // Loading button on request start; restore on every terminal event. |
| @@ -9,6 +9,25 @@ | |||
| 9 | 9 | type?: string; | |
| 10 | 10 | } | |
| 11 | 11 | ||
| 12 | + | /** How long the fade-out animation runs before the node is removed. Matches the | |
| 13 | + | * `.fade-out` transition in the stylesheet, so shortening it here drops the | |
| 14 | + | * toast mid-animation. */ | |
| 15 | + | const TOAST_FADE_MS = 300; | |
| 16 | + | ||
| 17 | + | /** Fade `toast` out after `lifetimeMs`, then remove it. | |
| 18 | + | * | |
| 19 | + | * Extracted because `initHtmxGlue`'s error toast had its own verbatim copy of | |
| 20 | + | * the same two nested timers. The lifetime stays the caller's business: this | |
| 21 | + | * renderer's toasts live 3000ms and an error toast lives 6000ms, and which of | |
| 22 | + | * those is right is a question for the semantic-timing work rather than for a | |
| 23 | + | * helper that only exists to stop the dismissal being written twice. */ | |
| 24 | + | export function autoDismiss(toast: HTMLElement, lifetimeMs: number): void { | |
| 25 | + | setTimeout(() => { | |
| 26 | + | toast.classList.add('fade-out'); | |
| 27 | + | setTimeout(() => toast.remove(), TOAST_FADE_MS); | |
| 28 | + | }, lifetimeMs); | |
| 29 | + | } | |
| 30 | + | ||
| 12 | 31 | /** Fire a toast. Rendered by the delegated listener installed by `initToasts`. */ | |
| 13 | 32 | export function showToast(message: string, type = 'error'): void { | |
| 14 | 33 | document.body.dispatchEvent(new CustomEvent<ToastDetail>('showToast', { detail: { message, type } })); | |
| @@ -28,9 +47,6 @@ | |||
| 28 | 47 | toast.className = 'toast toast-' + (detail.type || 'info'); | |
| 29 | 48 | toast.textContent = detail.message || 'Action completed'; | |
| 30 | 49 | container.appendChild(toast); | |
| 31 | - | setTimeout(() => { | |
| 32 | - | toast.classList.add('fade-out'); | |
| 33 | - | setTimeout(() => toast.remove(), 300); | |
| 34 | - | }, 3000); | |
| 50 | + | autoDismiss(toast, 3000); | |
| 35 | 51 | }); | |
| 36 | 52 | } |