Align Trail Status with Server and Refactor List

Claude Code·Fable 5·matthiaswenz·3mo ago·1hr 21min·3 Checkpoints·11 file changes·+313/-317·82.4K tokens

pull and merge in latest main from the remote - then tell me the semantical diff on this current branch

3mo ago·59.2s

Merged origin/main cleanly (the auto-merge in cmd/entire/cli/trail_cmd.go resolved fine — the branch's tests still pass post-merge).

Semantic diff vs main

This branch is a single commit (ee8d7d0a6) that improves the empty-state message of entire trail list. The problem it fixes: entire trail list filters to in_progress trails by default, so when a repo has trails but none in progress, the old flat "No trails found." message misleadingly suggested the repo had no trails at all.

Changes in cmd/entire/cli/trail_cmd.go:

  • The inline empty-state printing in runTrailListAll is extracted into a new helper, printTrailListEmpty(w, authorFilter, statusFilters).
  • The message now names the active status filter, e.g. "No in progress trails found." instead of "No trails found." When the user passed --status any (no filter), it stays generic.
  • When a status filter is active, it adds a hint: "Use --status any to see trails in other statuses."
  • If an author filter is active, it's appended too: "No in progress trails found for alice."
  • The existing "Commands:" block (trail create / list / update) is unchanged at the end.

Tests added in trail_cmd_test.go (three new parallel unit tests): default status filter names the filter and shows the --status any hint; no filter (--status any) omits the hint and uses the generic message; author filter appears in the message.

Net: +69/−6 lines across the two files — purely a UX/messaging change, no behavior change to which trails are listed.

Make sure that the possible values for a trail's status match the ones existant in the API/data model on the service / database layer

3mo ago·5m

All status validation flows through trail.ValidStatuses(), so create, update, and list --status all automatically reflect the new set.

Done — CLI trail statuses now match the service

The service simplified the status model on June 3rd (migration 20260603120300_simplify_repo_trail_status in entire.io): in_progress and in_review were folded into open, the DB check constraint and the API TrailStatus type are now exactly draft | open | merged | closed. The CLI was still carrying the old six-value set — and this branch's default trail list filter was in_progress, a status the server can no longer return, which would have made the default listing permanently empty.

Changes:

  • cmd/entire/cli/trail/trail.go — removed StatusInProgress and StatusInReview; ValidStatuses() is now draft, open, merged, closed, with a comment tying the set to the server's check constraint.
  • cmd/entire/cli/trail_cmd.go — default --status filter for trail list is now open (the status the server migrated the retired values into); the status grouping order is now open → draft → merged → closed; updated the empty-state comment.
  • Tests updated across trail_cmd_test.go, trail/trail_test.go, and trail/store_test.go: replaced retired statuses with open/draft, and added explicit assertions that in_progress/in_review are now rejected by both Status.IsValid() and the --status filter parser. Also refreshed two trail_review_cmd_test.go fixtures that used "in_review" (display-only, but stale).

Other enums already matched: priority (urgent/high/medium/low/none) and reviewer status (pending/approved/changes_requested) are identical on both sides. Trails with unknown statuses still land in the "Other" bucket on unfiltered lists, so any future server-side additions degrade gracefully.

Verified: go build ./..., both affected test packages pass, mise run fmt + mise run lint clean.

One thing to note: the CLI's Metadata still has a Type field (bug/feature/...) while the server migration comment mentions a dropped type column — I left that alone since you asked about status, but it may be worth the same treatment in a follow-up.

commit and push

3mo ago·35.9s

The full mise run check (unit + integration + e2e canary) is running in the background — I'll commit and push as soon as it passes.

3mo ago·56.3s

Committed and pushed. The full mise run check passed first (fmt, lint, unit + integration + e2e canary, ~2¼ min).

  • Commit 4767f3dc4 — "trail: align status values with the server's simplified set" (6 files, +51/−49), pushed to origin/fix-trail-list-empty-state with tracking set up. The branch also carries the earlier empty-state commit and the merge of latest main.
  • I left .opencode/package-lock.json out of the commit — the canary run bumped @opencode-ai/plugin 1.4.7 → 1.17.3 in the local environment, unrelated to this change. It's still modified in your working tree if you want to keep or discard it.

GitHub suggests opening the PR at: https://github.com/entireio/cli/pull/new/fix-trail-list-empty-state

open a PR

3mo ago·25.5s

PR opened: https://github.com/entireio/cli/pull/1425

It covers both commits on the branch: the empty-state message improvement and the status-set alignment with the server (draft, open, merged, closed, default filter now open). The "1 uncommitted change" warning is just the local .opencode/package-lock.json modification we intentionally left out.

clean up local git state

3mo ago·36.8s

Working tree is clean — I discarded the .opencode/package-lock.json modification that the e2e canary run left behind (it was just a local lockfile re-resolve, not part of your change). The branch is up to date with origin/fix-trail-list-empty-state.

One thing I deliberately left alone: there's a stash from other work — stash@{0}: WIP on feat/trail-api-migration: 0a22289d Update migrate.go. That looks like real WIP from another branch, so I didn't drop it. Say the word if you want it gone (git stash drop) or inspected first (git stash show -p).

drop the stash

3mo ago·12.7s

Dropped — the stash list is now empty, and local git state is fully clean. (If you ever need it back, the dropped stash commit was bd23bfb84 and stays recoverable via git stash apply bd23bfb84 until gc runs.)

check if CI passes on the PR

3mo ago·17.3s

CI is fully green on PR #1425 — all 10 checks pass: lint, binary-size, license check, Cursor Bugbot, and the whole test matrix (core, canary, and all three integration shards). It's ready for review/merge.

why does mise run install not work on this branch?

3mo ago·43.9s

It's not this branch — there is no install task in this repo at all. The failure you're seeing is mise erroring with "task not found" and printing the list of available tasks. I checked origin/main and the full git history of mise.toml and mise-tasks/: no install task ever existed or was deleted, so it's equally absent on main. (The only "install" in mise.toml is the go install postinstall hook for the Go toolchain.)

What you're probably after is one of these:

  • mise run dev:publish — the actual "install" task: builds and go installs entire and git-remote-entire into ~/go/bin
  • mise run build — just builds the CLI binary

So mise run dev:publish is the equivalent of what mise run install would do. You may be thinking of another repo's task naming.

however from ~/Development/entire/cli I can run mise run install

3mo ago

there's another inconsistency with the trail list output: when the number of trails matching the filters exceeds that limit count set explicitply via limitor implicitly via its default (10), the output just shows "10 trails". this suggests that's the matching number to the filter query, not just the value filtered down. so it should show xx/yyy trails Include this in the PR as well

additionally: find any other such potential inconsistencies in the trail list command

3mo ago·3m

While that runs, here's what I found and changed. The truncation-count inconsistency existed in three places, all now fixed with a shown/total format that only appears when --limit actually truncated the list:

  1. The header — Recent trails · 10 now renders as Recent trails · 10/37 (and pluralization follows the total, so Open · 1/3 trails, not trail).
  2. Per-status group counts in the grouped view — Open · 8 now renders Open · 8/20 when the page cut that group short.
  3. The "Other" bucket for unknown statuses — same treatment.

Other inconsistencies I found in trail list while auditing:

  • --json flag help said "respects --status filter" but JSON output also respects --author and --limit. Fixed the description.
  • Not fixed (judgment call): a status group can vanish entirely — if all of a status's trails fall outside the top-N by update time, its group is silently omitted from the grouped view. The new 10/37 header at least signals there's more than shown, but there's no per-status hint. Fixing it would mean rendering Merged · 0/15 rows, which felt noisy; flag it if you want that behavior.
  • Not fixed (by design): explicit --status can't name a server-added status the CLI doesn't know yet — parseTrailStatusFilter validates against ValidStatuses(). Unknown statuses are only reachable via --status any (where they land in "Other"). That's an intentional trade-off already documented in the code.

I'll commit and push once mise run check completes.

3mo ago·52.7s

Done. Commit adfe5a07b is pushed to the PR and the description is updated: https://github.com/entireio/cli/pull/1425

What the commit fixes — the truncation-count inconsistency in all three places it appeared:

  • Header: Recent trails · 10/37 (was · 10), with the noun pluralized by the total (Open · 1/3 trails)
  • Per-status group counts: Open · 8/20
  • "Other" bucket: same shown/total treatment
  • Plus the stale --json help text, which claimed only --status is respected (it also honors --author and --limit)

Untruncated lists keep plain counts — the slash form only appears when --limit actually dropped rows. Four new unit tests cover each case, and the full mise run check passed before the commit.

Two findings from the audit I deliberately left as-is: a status group whose trails all fall outside the top-N silently disappears from the grouped view (the 10/37 header now signals it, but rendering Merged · 0/15 rows felt noisy), and explicit --status can't name server-added statuses the CLI doesn't know — that's existing intentional behavior (--status any + "Other" bucket covers it). Happy to address either if you disagree.

there seems to be a bug that the max list of trails returned is always capped at 23. when the limit is lower, I'm seeing "x/23" returned in the CLI - when I set it higher it only says "23 trails" - but looking at the database and backend it's way more trails in that state - what's going on?

3mo ago·2m

Found it — it's a silent server-side pagination cap, and the CLI never pages past it.

What's going on

runTrailListAll calls GET /api/v1/trails/:host/:owner/:repo with no query parameters (trail_cmd.go:187). But the server paginates this endpoint: it defaults to limit=50 (max 200) and returns {trails, total, limit, offset, ...} (trails.ts:628-633 in entire.io). So the CLI only ever receives the first 50 trails, sorted by updated_at descending — then does all its status/author filtering client-side on that 50-row page.

Your "23" is simply how many trails in that filtered status exist among the 50 most recently updated trails. Everything older never left the server:

  • --limit 5 → 5 shown of 23 matches within the page → "5/23"
  • --limit 100 → all 23 page-local matches shown → "23 trails", even though the DB has far more

Two compounding factors:

  1. The CLI's TrailListResponse struct (api/trail_types.go) doesn't even decode the server's total/limit/offset fields, so it has no way to notice the response was truncated.
  2. My shown/total change is computed from the client-side count, so it inherits the same blind spot — the "total" it shows is really "total within the first server page."

The fix

The server already supports exactly what the CLI does by hand: status (comma-separated, OR'd in SQL), author (login, resolved server-side), limit (1–200), and offset. The CLI should push its filters down as query params and read the returned total for the shown/total display. Design points to settle:

  • --limit above 200 would need an offset loop (or just clamp and say so)
  • The per-status group counts in the --status any grouped view would be page-local unless we add a server aggregation — likely fine to keep them page-local since the header shows the real total
  • Client-side sorting becomes redundant (server already orders by updated_at desc)

Want me to implement the pushdown in this PR, or keep this PR display-only and do the pagination fix as a follow-up? Given it changes the request shape and has its own edge cases, I'd lean toward a separate PR.

please implement using the service side filtering logic

re the 200 limit: make it clear when we get capped there, but normally listing trails should not need that many results

for status any let's not do any aggregation or breakdown by status, just show the total count matched

3mo ago·6m

Implementation is complete; full mise run check is running and I'll commit, push, and update the PR when it's green. Summary of what changed:

Server-side filtering (trail_cmd.go)

  • runTrailListAll now sends status (comma-separated), author (the resolved login, including --author me), and limit as query params via a new trailListQuery helper — the server does the filtering, ordering (updated_at desc), and truncation.
  • TotalMatched now comes from the server's total field (newly decoded in api.TrailListResponse along with limit/offset), so 10/137 reflects the real database count, not the first-page count. Older servers without total degrade to plain counts.
  • All the client-side filter/sort/limit machinery is deleted (filterTrailsByAuthor, filterTrailsByStatuses, limitTrails, sort.Slice, …).

200-cap visibility — --limit above 200 is clamped in the request, and when matches actually exceed what was shown, the list is followed by: Note: --limit 500 exceeds the server maximum of 200 trails per request.

--status any display — per your call: no per-status aggregation or grouped breakdown anymore. It's one flat list with the header showing the total match count, and a STATUS column appears whenever more than one status can occur (so unknown server-side statuses also stay visible, replacing the old "Other" bucket).

Same-bug-class finding: findTrail (backing trail show, update, and by-number/by-branch lookups) also scanned the unpaginated first page — so entire trail on a branch whose trail wasn't in the 50 most recently updated would claim no trail exists. I applied a stopgap (request the 200 max) and left a comment; the real fix needs a server-side branch filter param or using the by-number detail endpoint, which I'd suggest as a follow-up.

3mo ago·59.5s

Pushed and PR updated: https://github.com/entireio/cli/pull/1425

  • Commit 9f453fc10 — "trail list: push filters, limit, and pagination to the server" (+123/−247; the deleted client-side filter/sort/limit machinery accounts for the negative balance). Full mise run check passed before committing.
  • The PR body now covers all four changes on the branch, plus a Follow-up section flagging the remaining findTrail blindness past 200 trails (needs a server-side branch filter or the by-number detail endpoint — a service-repo change).

With this, your "capped at 23" symptom is fixed at the root: the 23 was just the matching slice of the first 50-row server page. Now the server filters and counts, so you'll see the real totals (e.g. 10/137), and anything beyond a 200-row request gets an explicit cap note instead of silently vanishing.