fix(e10): review round 1, part 2 — the docs the epic invalidated, and three contracts made real
Finishes round 1. Part 1 took the runtime bugs; this takes the prose that had gone false and the three "the comment says so" gaps behind it. CLAUDE.md is auto-loaded into every agent session and ui/README.md is not, so an architecture paragraph that still described a flat components bag was the most expensive stale sentence in the repo: the next agent adding a popover reads it, sees no platform layer, and writes the generic component into components/ — the invisible-layer failure this epic exists to end. It now names components/ui/ and says the boundary is lint-enforced. The canonical-patterns table gained the three rows the epic established: ui/Button over a hand-rolled <button>, a keymap row over a private keydown listener, and disabled-when-busy vs inert-when-inapplicable. ui/README.md's inventory was written in this branch's first commit and never learned about CommandPalette, ContextMenu/useContextMenu, KeyboardHelp or gridColumns — in a section whose own closing rule is that a second component painting the same thing is how the last inconsistency happened. It also claimed "everything is rem-derived" while VirtualGrid's minCol/GRID_GAP are deliberately px; that is now stated as the exception it is, and a new section records which promises here are tests rather than sentences. Three contracts stopped being prose: - A text-less Button must carry a name. "Required for iconOnly" was a doc comment, which is why the migration shipped two nameless icon buttons through eight green lanes. It is a union now, covering both spellings (iconOnly, and size="icon"/"icon-sm"), satisfied by label, aria-label or title. It caught one more on the way in: Banner's dismiss took an optional dismissTitle, so it could render unnamed — it falls back to common.close now. - gridColumns' gutter arithmetic. Every existing assertion survived replacing floor((w + GAP) / (minCol + GAP)) with the naive floor(w / (minCol + GAP)): the fixed widths agree, the capped cases are cap-bound, and the loops compare columnsFor against itself. Pinned at the exact-fit boundaries where the two first disagree (536 and 828). - ShareDialog's link row. Only Copy was migrated in S1, leaving one 2rem button between two 1.75rem ones — and on a coarse pointer .touch-target grew only the migrated one to 2.75rem while the destructive Revoke stayed small. Also: the maxCols docstring said 8 columns of 384px where the test it shipped with asserts 369.
This commit is contained in:
@@ -138,27 +138,37 @@ function LinkRow({ link, onChanged }: { link: ShareLink; onChanged: () => void }
|
||||
value={fullUrl}
|
||||
className="flex-1 min-w-0 bg-transparent text-xs text-muted truncate outline-hidden"
|
||||
/>
|
||||
<Button variant="ghost" size="icon-sm" onClick={copy} title={t("downloads.share.copy")}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={copy}
|
||||
aria-label={t("downloads.share.copy")}
|
||||
title={t("downloads.share.copy")}
|
||||
>
|
||||
{copied ? <Check className="w-4 h-4 text-success" /> : <Copy className="w-4 h-4" />}
|
||||
</Button>
|
||||
<button
|
||||
{/* All three on the same rung. Only Copy was migrated in S1, which left one 2rem button
|
||||
between two 1.75rem ones with a different radius and hover fill — and, on a coarse
|
||||
pointer, `.touch-target` grew ONLY the migrated one to 2.75rem while the destructive
|
||||
Revoke stayed small (E10 review round 1). */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={() => {
|
||||
setNewPass("");
|
||||
setEditPass((v) => !v);
|
||||
}}
|
||||
title={
|
||||
className={link.has_password ? "text-accent" : undefined}
|
||||
label={
|
||||
link.has_password
|
||||
? t("downloads.share.changePassword")
|
||||
: t("downloads.share.setPassword")
|
||||
}
|
||||
className={clsx(
|
||||
"p-1.5 rounded-md hover:bg-surface transition",
|
||||
link.has_password ? "text-accent" : "text-muted hover:text-fg",
|
||||
)}
|
||||
>
|
||||
<Lock className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
icon={<Lock className="w-4 h-4" />}
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={async () => {
|
||||
if (
|
||||
await confirm({
|
||||
@@ -170,11 +180,10 @@ function LinkRow({ link, onChanged }: { link: ShareLink; onChanged: () => void }
|
||||
)
|
||||
revoke.mutate();
|
||||
}}
|
||||
title={t("downloads.share.revoke")}
|
||||
className="p-1.5 rounded-md hover:bg-surface text-muted hover:text-danger transition"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
className="enabled:hover:text-danger"
|
||||
label={t("downloads.share.revoke")}
|
||||
icon={<Trash2 className="w-4 h-4" />}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-muted flex-wrap">
|
||||
<span className="inline-flex items-center gap-1">
|
||||
|
||||
Reference in New Issue
Block a user