Skip to main content

max / makenotwork

Delete the two hand-rolled sort carets in mnw-server's style.css Three vocabularies for one caret. `.data-table th.sortable` and its four rules had no consumer at all -- nothing in templates/ ever put `sortable` on a `th`. `.sortable-th` plus `.sort-arrow` / `.sort-arrow--active` had one, the project content tab, whose JS wrote the glyph as textContent and toggled a modifier class, so assistive tech was told nothing. Meanwhile the generated `.table-heading[data-sortable]` and `.table-heading[aria-sort]::after` in static/layout.css matched no element in the app. The template's headers move onto `.table-heading` with `data-sortable`, tab-project-content.js sets `aria-sort` and nothing else, and both overrides are gone. What stays is the focus ring, the hover surface and the caret's reserved gap, none of which makeover answers for a heading.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-12 00:44 UTC
Signed with PGP, not checked
Commit: 1d78aa708b4cf251111e9f1f77e3e52b360449b4
Parent: a6485df
3 files changed, +71 insertions, -39 deletions
@@ -880,26 +880,12 @@
880 880 font-weight: normal;
881 881 }
882 882
883 - .data-table th.sortable {
884 - cursor: pointer;
885 - transition: background 0.2s ease;
886 - user-select: none;
887 - }
888 -
889 - .data-table th.sortable:hover {
890 - background: var(--border);
891 - }
892 -
893 - .data-table th.sortable:focus-visible {
894 - outline: 2px solid var(--focus-ring);
895 - outline-offset: 2px;
896 - }
897 -
898 - .data-table th.sortable::after {
899 - content: " ^";
900 - opacity: 0.3;
901 - font-size: var(--text-fine);
902 - }
883 + /* `.data-table th.sortable` and its four rules were here and had no consumer:
884 + nothing in templates/ ever put `sortable` on a `th`. The one sortable table
885 + in the app is templates/partials/tabs/project_content.html, which used
886 + `.sortable-th` further down this file. Both are gone; the affordance and the
887 + caret are `.table-heading[data-sortable]` in the generated static/layout.css.
888 + See the block that replaced `.sortable-th` for what stayed behind. */
903 889
904 890 .data-table tbody tr {
905 891 transition: background 0.1s ease;
@@ -8681,11 +8667,51 @@
8681 8667 margin-bottom: var(--gap-bound);
8682 8668 }
8683 8669
8684 - /* Sortable table column header */
8685 - .sortable-th { cursor: pointer; }
8686 - .sortable-th:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
8687 - .sort-arrow { opacity: 0.4; }
8688 - .sort-arrow--active { opacity: 1; }
8670 + /* Sortable table column header
8671 +
8672 + The cursor and the caret are NOT here. `.table-heading[data-sortable]` and
8673 + `.table-heading[aria-sort="..."]::after` are generated into static/layout.css
8674 + by makeover-webview out of `Column::sortable` and `Column::sorted`. This file
8675 + carried two hand-written copies of the same fact -- `.sortable-th` here and
8676 + `.data-table th.sortable` up at the data-table block -- and a third channel in
8677 + tab-project-content.js, which wrote the glyph as text and toggled
8678 + `.sort-arrow--active`. Three vocabularies, one state, and the generated pair
8679 + matched nothing because no heading carried `.table-heading`.
8680 +
8681 + The focus ring stays: it is this app's focus treatment, not a restatement of
8682 + the caret, and makeover emits no focus rule for a heading. */
8683 + .table-heading[data-sortable] {
8684 + user-select: none;
8685 + transition: background 0.2s ease;
8686 + }
8687 +
8688 + .table-heading[data-sortable]:hover {
8689 + background: var(--border);
8690 + }
8691 +
8692 + .table-heading[data-sortable]:focus-visible {
8693 + outline: 2px solid var(--focus-ring);
8694 + outline-offset: 2px;
8695 + }
8696 +
8697 + /* makeover-tui and makeover-immediate emit their caret with a leading space;
8698 + the webview emitter appends the bare glyph, so the gap is the app's to put
8699 + back. Reserved on every sortable heading and not only the sorted one, or
8700 + pressing a header reflows the row it sits in.
8701 +
8702 + Box only, never `content`. This file outranks the `makeover` layer, so
8703 + declaring `content` on the sorted arm would win over the generated caret and
8704 + put the arrow back to nothing -- the same override this block deletes, one
8705 + property along. The empty box goes on the arm the generator does not match. */
8706 + .table-heading[data-sortable]:not([aria-sort])::after {
8707 + content: '';
8708 + }
8709 +
8710 + .table-heading[data-sortable]::after {
8711 + display: inline-block;
8712 + min-width: 1ch;
8713 + margin-left: var(--gap-bound);
8714 + }
8689 8715
8690 8716 /* Data-table column-width utilities (numeric width hints,
8691 8717 kept as classes to avoid inline `style="width: NN%"`) */
@@ -254,15 +254,21 @@
254 254 }
255 255 }
256 256
257 - var arrows = document.querySelectorAll('.sort-arrow');
258 - for (var i = 0; i < arrows.length; i++) {
259 - var col = parseInt(arrows[i].dataset.col, 10);
260 - if (col === colIndex) {
261 - arrows[i].textContent = contentSortState.asc ? '▲' : '▼';
262 - arrows[i].classList.add('sort-arrow--active');
257 + // One channel, and it is `aria-sort`. The caret is drawn by the generated
258 + // `.table-heading[aria-sort]::after` in static/layout.css, so the arrow and
259 + // the order a screen reader announces cannot disagree. This used to write
260 + // the glyph itself into a `.sort-arrow` span and toggle a modifier class,
261 + // which said nothing to assistive tech and duplicated rules the stylesheet
262 + // already had.
263 + //
264 + // `data-arg` is the column index the heading already carries for the sort
265 + // itself, so the heading is matched by the same number it dispatches on.
266 + var headings = table.querySelectorAll('[data-sortable]');
267 + for (var i = 0; i < headings.length; i++) {
268 + if (parseInt(headings[i].dataset.arg, 10) === colIndex) {
269 + headings[i].setAttribute('aria-sort', contentSortState.asc ? 'ascending' : 'descending');
263 270 } else {
264 - arrows[i].textContent = '';
265 - arrows[i].classList.remove('sort-arrow--active');
271 + headings[i].removeAttribute('aria-sort');
266 272 }
267 273 }
268 274 }
@@ -62,12 +62,12 @@
62 62 <tr>
63 63 <th class="col-3"><input type="checkbox" id="select-all" data-change="toggleSelectAllFromEl"></th>
64 64 <th class="col-5">#</th>
65 - <th class="col-32 sortable-th" tabindex="0" data-action="sortContent" data-arg="2" data-arg2="text" title="Sort by title">Item <span class="sort-arrow" data-col="2"></span></th>
66 - <th class="col-12 sortable-th" tabindex="0" data-action="sortContent" data-arg="3" data-arg2="text" title="Sort by type">Type <span class="sort-arrow" data-col="3"></span></th>
67 - <th class="col-10 sortable-th" tabindex="0" data-action="sortContent" data-arg="4" data-arg2="money" title="Sort by price">Price <span class="sort-arrow" data-col="4"></span></th>
68 - <th class="col-10 sortable-th" tabindex="0" data-action="sortContent" data-arg="5" data-arg2="num" title="Sort by sales">Sales <span class="sort-arrow" data-col="5"></span></th>
69 - <th class="col-10 sortable-th" tabindex="0" data-action="sortContent" data-arg="6" data-arg2="money" title="Sort by revenue">Revenue <span class="sort-arrow" data-col="6"></span></th>
70 - <th class="col-8 sortable-th" tabindex="0" data-action="sortContent" data-arg="7" data-arg2="text" title="Sort by status">Status <span class="sort-arrow" data-col="7"></span></th>
65 + <th class="col-32 table-heading" data-sortable tabindex="0" data-action="sortContent" data-arg="2" data-arg2="text" title="Sort by title">Item</th>
66 + <th class="col-12 table-heading" data-sortable tabindex="0" data-action="sortContent" data-arg="3" data-arg2="text" title="Sort by type">Type</th>
67 + <th class="col-10 table-heading" data-sortable tabindex="0" data-action="sortContent" data-arg="4" data-arg2="money" title="Sort by price">Price</th>
68 + <th class="col-10 table-heading" data-sortable tabindex="0" data-action="sortContent" data-arg="5" data-arg2="num" title="Sort by sales">Sales</th>
69 + <th class="col-10 table-heading" data-sortable tabindex="0" data-action="sortContent" data-arg="6" data-arg2="money" title="Sort by revenue">Revenue</th>
70 + <th class="col-8 table-heading" data-sortable tabindex="0" data-action="sortContent" data-arg="7" data-arg2="text" title="Sort by status">Status</th>
71 71 <th class="col-10">Actions</th>
72 72 </tr>
73 73 </thead>