Skip to content

fix(web): a status name is not a category — stop guessing "done" (GDK-272) - #23

Merged
midagedev merged 1 commit into
mainfrom
fix/gdk272
Aug 18, 2026
Merged

fix(web): a status name is not a category — stop guessing "done" (GDK-272)#23
midagedev merged 1 commit into
mainfrom
fix/gdk272

Conversation

@midagedev

Copy link
Copy Markdown
Owner

RESOLVED_STATUS_NAMES held resolved/closed/done/해결됨/종료/완료, and
two readers consulted it whenever the server sent no category: effectiveCategory
returned done on a name match, and HistoryTimeline's local isReopen painted a
reopen badge from isResolved(from) && !isResolved(to).

This is the trap the repo names first. status = 'In Progress' is silently zero
rows on a Korean account; docs/STATE_OF_PLAY.md hard-won #4 says a reopen is a
done-category → non-done transition and never a name match. The set's own
comment claimed it kept "only generic names that mean the same on every Jira" —
완료 is not a generic name, so the premise contradicted itself.

It was not dead code

internal/server/read.go:445 fills from_category from
view.categories[FromID], and read.go:650 seeds that map only from status ids
a currently-mirrored issue occupies
. Any status the issue passed through that
nothing currently holds — a status removed from the workflow, say — arrives with
no category, and the name decides.

So it also broke on an English site with a custom done status ("Verified",
"Shipped"), not only on a third language. The audit round rated this
medium-confidence; verifying that seeding path is what promoted it to a
confirmed Highest defect.

What changed

  • effectiveCategory trusts a real new|inprogress|done and otherwise returns
    inprogress. No name is consulted.
  • isReopen moved into view-config.ts as the single owner of the reopen
    rule, and returns false when both categories are empty: an unpainted badge is a
    missing hint, a wrongly painted one is a false claim about the issue's history.
  • matchesIdFirst's name fallback is deliberately untouched — that is the
    documented compatibility path for legacy saved views, a different decision.

Recurrence, and a correction to my own spec

tools/doc-checks.sh now matches '해결됨', '종료', '완료' as display-name
literals in web logic. Its old comment said this table was "deliberately not
matched (would fail the current tree; sibling issue)"
— the round found that was
only a comment, not a grep exclusion; view-config.ts was already in the scan
path. So the work was adding patterns that catch the table, not removing an
exemption. My spec had that wrong and the round said so.

Lowercase done/resolved/closed stay out of the pattern on purpose: they are
also category keys and other-field values (a RangeField resolved, a GitHub PR
closed).

FAIL-first run by me: reintroducing new Set(['완료', '해결됨']) into
view-config.ts makes doc-checks fail and name the file and line; removing it
returns exit 0.

effectiveCategory had no unit test at all before this. 11 new cases now
cover the trusted values, an empty category with a Korean status name, 'Done',
a custom 'Shipped', and the reopen predicate.

Debuggability

The failure mode was silence — nobody could tell a category was missing and
something guessed. missingStatusCategorySeen() counts how often a category
decision ran without a category. Integer increment, no per-row logging.

Gates (lead, cold)

svelte-check 4250 files / 0 errors · vitest 34 files / 347 tests (was
336) · tools/doc-checks.sh all passed · Playwright 240 passed (mandatory
here — web/ changed).

Closes GDK-272.

🤖 Generated with Claude Code

…-272)

`RESOLVED_STATUS_NAMES` held `resolved`/`closed`/`done`/`해결됨`/`종료`/`완료`
and two readers used it whenever the server sent no category:
`effectiveCategory` returned `done` on a name match, and HistoryTimeline's local
`isReopen` painted a reopen badge from `isResolved(from) && !isResolved(to)`.

The repository forbids exactly this. `status = 'In Progress'` is silently zero
rows on a Korean account; `docs/STATE_OF_PLAY.md` hard-won #4 says a reopen is a
done-category → non-done transition and *never* a name match. The set's own
comment claimed it kept "only generic names that mean the same on every Jira" —
`완료` is not a generic name, so the premise contradicted itself.

It was not dead code. `internal/server/read.go:445` fills `from_category` from
`view.categories[FromID]`, and `read.go:650` seeds that map only from status ids
a *currently mirrored* issue occupies. Any status the issue passed through that
nothing currently holds — a status removed from the workflow, for instance —
arrives with no category, and the name decides. So this also broke on an English
site with a custom done status ("Verified", "Shipped"), not just on a third
language.

Now: a real `new|inprogress|done` is trusted, anything else is `inprogress`, and
no name is ever consulted. `isReopen` moved into `view-config.ts` as the single
owner of the reopen rule and returns false when both categories are empty — an
unpainted badge is a missing hint, while a wrongly painted one is a false claim
about the issue's history.

`matchesIdFirst`'s name fallback is untouched: that is the documented
compatibility path for legacy saved views, a different decision from this one.

Recurrence: `tools/doc-checks.sh` now matches `'해결됨'`, `'종료'` and `'완료'` as
display-name literals in web logic. The old comment there claimed this table was
"deliberately not matched (would fail the current tree; sibling issue)" — the
round found that was only a comment, not a grep exclusion, so the real work was
adding patterns that catch the table rather than removing an exemption. Lowercase
`done`/`resolved`/`closed` stay out on purpose: they are also category keys and
other-field values (a RangeField `resolved`, a GitHub PR `closed`).

FAIL-first, run by the lead: reintroducing `new Set(['완료', '해결됨'])` into
view-config.ts makes doc-checks fail and name the file and line; removing it
returns exit 0. `effectiveCategory` also had no unit test at all before this —
now 11 new cases cover the trusted values, the empty-category-with-Korean-name
row, `'Done'`, a custom `'Shipped'`, and the reopen predicate.

Debuggability: the failure was silent, so `missingStatusCategorySeen()` counts
how often a category decision ran without a category. Integer increment, no
per-row logging.

Gates (lead, cold): svelte-check 4250 files / 0 errors · vitest 34 files /
347 tests (was 336) · doc-checks all passed · Playwright 240 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@midagedev
midagedev merged commit 58aba65 into main Aug 18, 2026
6 checks passed
@midagedev
midagedev deleted the fix/gdk272 branch August 20, 2026 07:13
midagedev added a commit that referenced this pull request Sep 2, 2026
- init: `--replace-standalone` (the v0.19 spelling) is accepted again. The
  legacy-alias table mapped the *new* name onto itself, so the old flag fell
  through to the parser as unknown. Test parses both spellings. (GDK-1312)
- web: an issue with no origin page no longer advertises "Open in Built-in"
  — the palette row, the `o` shortcut, and the shortcuts sheet all branch on
  issueOriginUrl() being non-null; the detail header shows the key as a
  label instead of a dead link. e2e on a gadak-origin serve. (GDK-1313)
- migrate --to linear: pickLinearState selects by linear.StatusCategory(type)
  instead of a hand-written inverse, so a team whose only "done" state is
  canceled maps instead of failing. Test with a canceled-only team. (GDK-1314)

Backlog snapshot regenerated so doc-checks #23 sees the three keys.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
midagedev added a commit that referenced this pull request Sep 2, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
midagedev added a commit that referenced this pull request Sep 2, 2026
…e (GDK-1352)

User call, 2026-09-02, looking at the promo clips beside the app: the
paper terminal in those composites runs the full width of the frame, one
horizontal seam, and it reads as two floors. The dock in the app stopped
at the sidebar's edge and read as a corner. So the sidebar lives in row 1
now and the dock spans every column — the 2026-08-30 decision (GDK-1194,
"the bottom of the content row") amended, not reversed: the tracker is
still the stage and the terminal still takes height rather than width.
A terminal needs columns, not rows; a gadak one-liner is ~120 characters
and the issue-key tab strip gets the width too. The sidebar pays in
height, which its sections already scroll for.

The default height drops from 40% to a quarter of the window — a band,
not a second workspace; ~9 rows at 900px, the height the paper terminal
in the clips has always had. A session that wants more drags the handle
once and it persists. The overlay regime (<900px, a right-hand sheet) is
a different mode and is left alone.

e2e: the GDK-1194 geometry test now also asks that the dock starts at the
window edge and that the sidebar ends where the dock begins (FAIL-first on
the old grid: x read 272 against 0), and a new test pins the default band
at 22–28% so the constant cannot drift back. Snapshot: GDK-1352/1353/1354
go public (doc-checks #23).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant