Repository navigation
web(tideline): slice 2 — docs hub + help, shared states, offline/reconnect, changelog - #5743
Conversation
…slice 2) Documentation and help become full Tideline surfaces on the existing dictionary/content spine: - Docs hub searches two registries: a task-based index (lib/docs-tasks.ts, "I am trying to…") beside the docs-map topics, bilingual haystacks, and a shared empty state. Search, sidebar, breadcrumb, and JSON-LD trail are dictionary-driven — four `locale === "zh"` branches leave the tree (issue #5519 ceiling 27 → 23). - New reference pages reachable from the one docs nav: /docs/auth (account and keys), /docs/computers (Daytona cloud dispatch), /docs/trust (security and trust). Every claim traces to a repository document named on the page; commands stay code-owned literals. - Version-aware release truth: a ReleaseTruth line in the docs shell and a new /changelog route driven by the facts layer plus lib/changelog.generated.ts, derived at prebuild from CHANGELOG.md (scripts/derive-changelog.mjs; lib/changelog.test.ts is the drift gate). Footer Product column links Changelog in all 18 locales. - Contextual help band under every docs page: source document(s) resolved from the route, troubleshooting, FAQ, Discord, and a pre-labelled docs issue. - Shared surface states (components/surface-state.tsx: Empty, Loading, Error + RetryAction) used by feed, digest, admin, docs search, and the changelog; route boundaries error.tsx / not-found.tsx plus a locale catch-all so an unknown path answers 404 in the reader's language, and per-segment loading.tsx on the request-time data pages. - Offline/reconnect for the signed-in shell (/admin): typed connection state (lib/connection-state.ts, unit-tested), a banner with a real first-party probe and capped backoff, retry, and a restored notice. No data is faked while disconnected. Evidence: tsc clean; eslint clean; `npm test` 43 files / 356 passed; check:facts OK, check:docs PASS, check:locales PASS + GT catalog OK; `next build` 696 static pages; 48 screenshots at 390/768/1440 with no horizontal overflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aDEyM2a4pPZ9qqMDrP5YX Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…TA, changelog reach Review findings on #61 slice 2, resolved: - Drop loading.tsx from the SSG/ISR segments (docs, feed, digest, roadmap). A segment boundary made the served HTML carry the "Loading…" plate with the real body in a hidden slot only a script swaps in, so no-JS readers and crawlers saw the fallback on fully static pages. Only /admin (force-dynamic) keeps its boundary. Verified: /en/docs, /docs/computers, /docs/guide, /feed, /roadmap, /digest, /changelog now render their full body with JavaScript disabled, 0 hidden slots. - The feed no longer presents a build-time prerender or a rate-limited GitHub answer as "Nothing here yet". lib/github.ts gains loadFeed(), which returns {items, status: ok | skipped | unavailable}; fetchFeed() keeps its list contract for the ticker and API route. /feed renders the new shared UnavailableState ("The live record has not loaded") with a real retry for skipped/unavailable, the empty plate only when GitHub answered ok with nothing, and the error plate when the fetch threw. - 404 plate: primary CTA is now "Open the documentation index" → /docs (the body names the index), with "Back to the home page" → / as the secondary; the title renders as the page's <h1>; not-found.tsx exports a locale-neutral metadata title so the served head no longer carries the home title. - Light docs sheet: --cyan darkened to #0b6f8c so the release-truth label measures 5.3:1 on --paper (was 4.29:1). - Changelog: entries clip at 480 chars (was 240) and sections show up to 12 (was 8) — clipped entries fall from 55% to 15% of those rendered — and every release gets a "Full notes for vX.Y.Z" deep link into CHANGELOG.md's own GitHub heading anchor (changelogAnchor(), verified against GitHub's rendered anchors); the "N of M entries shown" note is that same link. - Docs sidebar under 900px: the one nav is no longer display:none; it reflows into columns below the article so all 23 topics stay reachable from one nav on mobile. Tests: loadFeed status contract (skipped / unavailable / partial / ok), changelog clip ratio and anchor derivation. Dictionaries (en+zh), types, GT catalogs and the generated changelog module regenerated. Signed-off-by: CodeWhale Bot <bot@codewhale.net>
| dispatch: "codewhale dispatch", | ||
| kind: "kind=cloud", | ||
| remoteFlag: "--remote", | ||
| apiKey: "export DAYTONA_API_KEY=…", |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…elp band; security contact is hunter@codewhale.net Signed-off-by: CodeWhale Bot <bot@codewhale.net>
| dispatch: "codewhale dispatch", | ||
| kind: "kind=cloud", | ||
| remoteFlag: "--remote", | ||
| apiKey: "export DAYTONA_API_KEY=…", |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
| dispatch: "codewhale dispatch", | ||
| kind: "kind=cloud", | ||
| remoteFlag: "--remote", | ||
| apiKey: "export DAYTONA_API_KEY=…", |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
|
I'll analyze this and get back to you. |
|
Claude finished @Hmbown's task in 2m 12s —— View job Claude's review
Scope note: this PR only touches False positive: the standing "hardcoded secret" blocker
apiKey: "export DAYTONA_API_KEY=…",This is a display literal in a CorrectnessReviewed the new abstractions closely; no bugs found:
Reuse / simplification
Tests
No blocking issues found. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 293328dce9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // `RetryAction` without a handler re-runs this server render, which on an | ||
| // ISR page picks up the next revalidation — the real fix for "not loaded". | ||
| const states = getStates(locale); | ||
| const retry = <RetryAction label={states.retry} />; |
There was a problem hiding this comment.
Make the feed retry bypass the ISR cache
When the production build records skipped, this route caches that unavailable state for 600 seconds, but RetryAction without a handler only calls router.refresh(). A user who clicks “Try again” before the ISR entry becomes stale receives the same cached server component without calling GitHub, so the advertised recovery does nothing; use an uncached fetch/retry endpoint or explicitly invalidate the cached route.
Useful? React with 👍 / 👎.
| case "probe-ok": { | ||
| const wasDown = state.status !== "online"; | ||
| return { | ||
| status: "online", |
There was a problem hiding this comment.
Ignore stale probe successes after going offline
If a heartbeat probe is in flight when the browser emits offline, a response that finishes successfully afterward dispatches probe-ok and unconditionally changes the state back to online. Because the browser is already offline, it may emit no second event, leaving the banner hidden until a later probe fails; abort or identify stale probes, or preserve offline when handling their success.
Useful? React with 👍 / 👎.
| }) { | ||
| const { locale } = await params; | ||
| const t = getDocsShell(locale); | ||
| const facts = await getFacts(); |
There was a problem hiding this comment.
Pin the documented candidate to the deployed build
In a rollback or any deployment sharing a KV snapshot written by a newer source revision, getFacts() deliberately selects the newer KV record (lib/facts.ts returns kv-source-newer). The docs and generated changelog still come from this worker's older build, so the release band incorrectly claims that these pages describe the newer candidate; use build-pinned facts for the documented version while resolving the latest published release separately.
Useful? React with 👍 / 👎.
| membershipTitle: "Who can dispatch", | ||
| membershipLead: | ||
| "Every Codewhale Agent surface authenticates to the same Codewhale membership — the {login} account session. Membership gates cloud agents; provider brands stay internal. Installing and running the local runtime needs no account at all.", |
There was a problem hiding this comment.
Do not require account membership for local dispatch
For a user with Daytona and forge credentials but no Codewhale account session, local codewhale dispatch still works: crates/cli/src/dispatch.rs::run_with discovers only remotes, Daytona credentials, and the local job store, and the CLI dispatches directly to it without account validation. This new guide therefore incorrectly tells users that membership gates the command it documents and may prompt unnecessary account creation; distinguish the managed Agent surfaces from local Daytona dispatch.
Useful? React with 👍 / 👎.
| /** The same security contact the footer publishes (components/footer.tsx). */ | ||
| const SECURITY_MAILTO = "mailto:hmbown@gmail.com"; |
There was a problem hiding this comment.
Share the security contact instead of duplicating it
This adds a second literal copy of the security address already embedded in components/footer.tsx; changing the project's contact in either location can leave the trust page directing vulnerability reports to a stale mailbox. Put the public contact in the shared content/link spine and consume it from both surfaces.
AGENTS.md reference: web/AGENTS.md:L9-L11
Useful? React with 👍 / 👎.
| // Releases are a garnish on the feed; the two list calls are the record. | ||
| const status: FeedLoadStatus = issuesRes.ok && pullsRes.ok ? "ok" : "unavailable"; |
There was a problem hiding this comment.
Track feed availability separately for each column
When exactly one GitHub list endpoint fails, this combined status marks both lists unavailable. If, for example, the issues request succeeds with a legitimate empty array while the pulls request returns 403, the issues column renders “the source did not answer” even though its source did answer; return per-list statuses so each empty column reflects its own response.
Useful? React with 👍 / 👎.
| // A not-found boundary receives no params, so the title is locale-neutral; | ||
| // without it the 404 would carry the home page's <title>. | ||
| export const metadata: Metadata = { title: "Not found · Codewhale" }; |
There was a problem hiding this comment.
Replace the inherited home metadata on 404 pages
For a catch-all URL such as /en/missing, this object overrides only the title, so the homepage metadata produced by [locale]/layout.tsx is still inherited for fields such as description, canonical URL, hreflang, and Open Graph URL. The 404 therefore advertises the home page as its canonical/share target; provide complete not-found metadata, including appropriate robots and alternates, rather than retaining page-specific home metadata.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Codewhale review
PR #5743 implements the Tideline web slice 2: docs hub and new docs pages, shared surface states, offline/reconnect banner, and a build-derived changelog. The diff is large but mostly well-structured with dictionary-driven copy and tests for changelog derivation and connection state. A few correctness and completeness concerns remain.
Findings
- [WARNING] Admin "Not configured" branch loses its page
(
web/app/[locale]/admin/page.tsx:84)
ErrorState renders its title as aby default. The replaced markup used an
for the only heading on this branch. Without titleAs="h1", the admin config-error page has no top-level heading, which is an accessibility and document-outline regression.
- [WARNING] Changelog footer link appears to be missing (
web/components/footer.tsx)
The PR description says a footer link to /changelog is added in all 18 locales, and the catalog adds chrome.footerChangelog, but the footer.tsx diff does not render that key. The dictionary entry is therefore dead and the changelog footer link is not actually surfaced. - [INFO] RetryAction busy state may not show during router.refresh() (
web/components/retry-action.tsx)
RetryAction uses useTransition's startTransition around router.refresh(), but router.refresh() is not a React state update, so isPending may not stay true for the server round-trip. The button may not appear disabled/aria-busy while the server render is in flight. Consider using a local busy state and awaiting an awaitable refresh if available.
Suggestions
-
web/app/[locale]/admin/page.tsx:84— Pass titleAs="h1" so the error title remains the page's main heading.title={isZh ? "未配置" : "Not configured"} titleAs="h1"
Assessment
The slice is largely solid: state handling is explicit, generated changelog is parity-tested, and the docs dictionary migration is thorough. Before merge, fix the lost admin heading and verify the changelog footer link is actually rendered; the retry busy-state concern is minor but worth addressing.
Advisory review by Codewhale (codewhale review --pr 5743 --post, head c60a147b06f3c6e1f8ca0e23c175356305d1dc39). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.
| <div className="mt-6" /> | ||
| <ErrorState | ||
| locale={locale} | ||
| title={isZh ? "未配置" : "Not configured"} |
There was a problem hiding this comment.
[WARNING] Admin "Not configured" branch loses its page
ErrorState renders its title as a
by default. The replaced markup used an
for the only heading on this branch. Without titleAs="h1", the admin config-error page has no top-level heading, which is an accessibility and document-outline regression.
| <div className="mt-6" /> | ||
| <ErrorState | ||
| locale={locale} | ||
| title={isZh ? "未配置" : "Not configured"} |
There was a problem hiding this comment.
Pass titleAs="h1" so the error title remains the page's main heading.
| title={isZh ? "未配置" : "Not configured"} | |
| title={isZh ? "未配置" : "Not configured"} | |
| titleAs="h1" |
Resolves the slice-2 web work against main: fleet→pod union, sitemap 94 entries, GT re-export, dead-code budget 446. Changelog conflicts take main's side (branches no longer carry changelog hunks; receipts are written to main at merge time). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LmeqaZAesoHjT8N9PR7S2c
Eight fixes from the #5743 review threads: 1. Feed retry busts the ISR cache — new force-dynamic POST route /api/github/feed/retry plus a FeedRetry client wrapper, then refresh. A plain refresh re-served the cached failure. 2. Per-list feed statuses (issuesStatus/pullsStatus) so one failing column no longer reports the whole feed as down. 3. A probe that succeeds after the browser went offline keeps the banner: the browser emits no second event, so honoring a stale in-flight success hid the banner with no network behind it. 4. Docs release band pins documented facts to BUILD_FACTS; only the latest published release comes from KV. 5. Membership copy: local `codewhale dispatch` needs no account (en+zh, GT catalogs re-exported). 6. Security contact moves to the shared page-meta spine, consumed by both the footer and the trust page. 7. 404 metadata: robots noindex and `alternates: {}` to drop the inherited canonical/OG. 8. Admin not-configured title renders as h1. web: 384 passed (384) across 45 files; eslint clean; tsc --noEmit clean. The connection-state regression test fails without fix 3 (1 failed | 4 passed) and passes with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LmeqaZAesoHjT8N9PR7S2c
Main gained the #5747/#5703 receipts, so the derived file drifted and its parity guard failed. Regenerated via scripts/derive-changelog.mjs: two new Added entries, itemCount 35 → 37. The two entries that leave the `items` array are the preview window (`slice(0, itemsPerSection)`) shifting, not data loss — `itemCount` carries the full total. web: 384 passed (384). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LmeqaZAesoHjT8N9PR7S2c
| dispatch: "codewhale dispatch", | ||
| kind: "kind=cloud", | ||
| remoteFlag: "--remote", | ||
| apiKey: "export DAYTONA_API_KEY=…", |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
|
Claude finished @Hmbown's task in 2m 37s —— View job Claude's review
Scope note: this PR is described as web-only, but its two merge commits ( Blocker: unbalanced braces in
|
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_27255f9b-fcbc-49f1-8569-24a13d53d16a) |
There was a problem hiding this comment.
🟡 Changes recommended
A Rust syntax/structure issue in crates/tui/src/tui/views/mod.rs prevents compilation and must be fixed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds Tideline slice 2 web surfaces: a docs hub with dictionary-driven navigation/help and release-truth, shared empty/loading/error route states, an offline/reconnect banner, and a build-derived /changelog page (plus i18n/catalog updates), alongside a small TUI config view tweak and a dead-code budget update.
Changes:
- Build-time changelog derivation (
derive-changelog) +/changelogroute + footer link across locales. - Docs hub/navigation improvements (tasks/topics index, breadcrumbs/sidebar/help bands, release-truth line) with new docs dictionaries (
auth,computers,trust) and shared state dictionaries. - Shared route/surface states (empty/loading/error/unavailable) + connection-state model + signed-in shell connection banner + feed truthfulness fixes.
File summaries
| File | Description |
|---|---|
| web/scripts/gt-site.mjs | Add new dictionary stems |
| web/scripts/derive-changelog.mjs | Generate changelog module |
| web/scripts/check-locales.mjs | Allow new optional dicts |
| web/scripts/changelog-lib.mjs | Changelog parser/renderer |
| web/scripts/changelog-lib.d.mts | TS types for parser |
| web/package.json | Run derive-changelog in build/dev |
| web/lib/page-meta.ts | Add shared contact/security emails |
| web/lib/i18n/links.ts | Add footer changelog link |
| web/lib/i18n/gt-site.test.ts | Increase timeout for GT check |
| web/lib/i18n/dictionaries/zh/states.ts | New shared states (zh) |
| web/lib/i18n/dictionaries/zh/docs-trust.ts | New trust docs dict (zh) |
| web/lib/i18n/dictionaries/zh/docs-shell.ts | Docs shell copy updates (zh) |
| web/lib/i18n/dictionaries/zh/docs-computers.ts | New computers docs dict (zh) |
| web/lib/i18n/dictionaries/zh/docs-auth.ts | New auth docs dict (zh) |
| web/lib/i18n/dictionaries/zh/chrome.ts | Add footerChangelog (zh) |
| web/lib/i18n/dictionaries/zh/changelog.ts | New changelog dict (zh) |
| web/lib/i18n/dictionaries/vi/chrome.ts | Add footerChangelog (vi) |
| web/lib/i18n/dictionaries/uk/chrome.ts | Add footerChangelog (uk) |
| web/lib/i18n/dictionaries/types.ts | Add new dict interfaces/fields |
| web/lib/i18n/dictionaries/tr/chrome.ts | Add footerChangelog (tr) |
| web/lib/i18n/dictionaries/ru/chrome.ts | Add footerChangelog (ru) |
| web/lib/i18n/dictionaries/pt-BR/chrome.ts | Add footerChangelog (pt-BR) |
| web/lib/i18n/dictionaries/pl/chrome.ts | Add footerChangelog (pl) |
| web/lib/i18n/dictionaries/ko/chrome.ts | Add footerChangelog (ko) |
| web/lib/i18n/dictionaries/ja/chrome.ts | Add footerChangelog (ja) |
| web/lib/i18n/dictionaries/it/chrome.ts | Add footerChangelog (it) |
| web/lib/i18n/dictionaries/index.ts | Wire new dict getters |
| web/lib/i18n/dictionaries/id/chrome.ts | Add footerChangelog (id) |
| web/lib/i18n/dictionaries/hi/chrome.ts | Add footerChangelog (hi) |
| web/lib/i18n/dictionaries/fr/chrome.ts | Add footerChangelog (fr) |
| web/lib/i18n/dictionaries/es/chrome.ts | Add footerChangelog (es) |
| web/lib/i18n/dictionaries/en/states.ts | New shared states (en) |
| web/lib/i18n/dictionaries/en/docs-trust.ts | New trust docs dict (en) |
| web/lib/i18n/dictionaries/en/docs-shell.ts | Docs shell copy updates (en) |
| web/lib/i18n/dictionaries/en/docs-computers.ts | New computers docs dict (en) |
| web/lib/i18n/dictionaries/en/docs-auth.ts | New auth docs dict (en) |
| web/lib/i18n/dictionaries/en/chrome.ts | Add footerChangelog (en) |
| web/lib/i18n/dictionaries/en/changelog.ts | New changelog dict (en) |
| web/lib/i18n/dictionaries/de/chrome.ts | Add footerChangelog (de) |
| web/lib/i18n/dictionaries/ca/chrome.ts | Add footerChangelog (ca) |
| web/lib/i18n/dictionaries/ar/chrome.ts | Add footerChangelog (ar) |
| web/lib/i18n/dictionaries.test.ts | Expand dict parity tests |
| web/lib/github.ts | Split fetchFeed vs loadFeed status |
| web/lib/github.test.ts | Add loadFeed coverage |
| web/lib/docs-tasks.ts | New task index registry |
| web/lib/docs-tasks.test.ts | Validate tasks resolve/search |
| web/lib/docs-map.ts | Add new docs topics |
| web/lib/docs-ia.test.ts | Update sitemap/hreflang expectations |
| web/lib/docs-breadcrumbs.ts | Dictionary-driven crumbs (no locale branch) |
| web/lib/connection-state.ts | Typed connection reducer/backoff |
| web/lib/connection-state.test.ts | Unit tests for connection state |
| web/lib/changelog.test.ts | Drift gate for generated changelog |
| web/gt-catalog/zh.json | GT catalog updates (zh) |
| web/gt-catalog/en.json | GT catalog updates (en) |
| web/components/surface-state.tsx | Shared empty/loading/error plates |
| web/components/route-state.tsx | Locale-aware route boundaries |
| web/components/retry-action.tsx | Shared retry control |
| web/components/release-truth.tsx | Docs release-truth chrome |
| web/components/footer.tsx | Footer mailboxes + changelog link |
| web/components/feed-retry.tsx | Feed retry with cache-bust |
| web/components/docs-sidebar.tsx | Dictionary-driven sidebar |
| web/components/docs-search.tsx | Hub search over tasks+topics |
| web/components/docs-help.tsx | Per-page help band |
| web/components/docs-breadcrumb.tsx | Breadcrumb aria from dictionary |
| web/components/connection-banner.tsx | Offline/reconnect banner UI |
| web/components/code-spans.tsx | Tokenized code spans + ref rows |
| web/app/sitemap.ts | Add new routes to sitemap |
| web/app/globals.css | Styles for new components/surfaces |
| web/app/api/github/feed/retry/route.ts | POST endpoint to revalidate feed |
| web/app/[locale]/not-found.tsx | Locale not-found boundary |
| web/app/[locale]/feed/page.tsx | Honest feed empty/unavailable handling |
| web/app/[locale]/error.tsx | Locale error boundary plate |
| web/app/[locale]/docs/trust/page.tsx | New trust docs page |
| web/app/[locale]/docs/layout.tsx | Docs shell: release truth + help |
| web/app/[locale]/docs/computers/page.tsx | New computers docs page |
| web/app/[locale]/docs/auth/page.tsx | New auth docs page |
| web/app/[locale]/digest/page.tsx | Use shared EmptyState |
| web/app/[locale]/changelog/page.tsx | New changelog route |
| web/app/[locale]/admin/page.tsx | Add connection banner + shared states |
| web/app/[locale]/admin/loading.tsx | Admin segment loading boundary |
| web/app/[locale]/[...rest]/page.tsx | Locale catch-all 404 boundary |
| scripts/dead-code-budget.json | Increase dead-code budget ceiling |
| crates/tui/src/tui/views/mod.rs | TUI config view row change |
Review details
- Files reviewed: 84/84 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| export async function POST() { | ||
| revalidatePath("/[locale]/feed", "page"); | ||
| revalidatePath("/feed", "page"); | ||
| return NextResponse.json({ revalidated: true, at: new Date().toISOString() }); |
| try { | ||
| const controller = new AbortController(); | ||
| const timeout = window.setTimeout(() => controller.abort(), 8_000); | ||
| const res = await fetch(probeUrl, { | ||
| method: "GET", | ||
| cache: "no-store", | ||
| signal: controller.signal, | ||
| headers: { Accept: "application/json" }, | ||
| }); | ||
| window.clearTimeout(timeout); | ||
| dispatch({ type: res.ok ? "probe-ok" : "probe-failed", at: Date.now() }); | ||
| } catch { | ||
| dispatch({ type: "probe-failed", at: Date.now() }); | ||
| } finally { | ||
| inFlight.current = false; | ||
| } |
| "total": 448, | ||
| "per_crate": { | ||
| "tools": 2, | ||
| "tui": 414 | ||
| "tui": 446 |
| <a href={DISCORD_URL} target="_blank" rel="noreferrer"> | ||
| {t.helpDiscord} | ||
| </a> | ||
| <a href="mailto:help@codewhale.net">help@codewhale.net</a> | ||
| <a href={issueUrl} target="_blank" rel="noreferrer"> |
The main merge staged on this branch did not compile. `views/mod.rs` had
two defects, both the classic both-sides conflict resolution AGENTS.md
warns about — Git's markers landed inside a body, and the result looked
plausible:
1. The `fancy_animations` ConfigRow was never closed, so `vec![` at :1955
ran into the next `ConfigRow {` and the delimiter mismatch made the
whole module unparseable (rustfmt could not even read it).
2. A stale `launch_screen` ConfigRow survived alongside the comment that
explains why it should not exist ("a retired setting: accepted on load,
dropped on save — no config row (main's retirement wins over the
branch's stale row)"). The field is gone from `Settings`, so it did not
compile either. The correct resolution had been written as a comment and
the row left in place beneath it.
cargo check -p codewhale-tui: clean. cargo fmt --all --check: exit 0.
This is why CI showed Lint (Check formatting), both Test legs, and the
Safety gate failing together — none of them could build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LmeqaZAesoHjT8N9PR7S2c
| dispatch: "codewhale dispatch", | ||
| kind: "kind=cloud", | ||
| remoteFlag: "--remote", | ||
| apiKey: "export DAYTONA_API_KEY=…", |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_b23f7f61-8bb3-44da-bbf3-4d6dc14c42d4) |
|
Claude finished @Hmbown's task in 1m 43s —— View job Claude's review
Scope note: since the last review round (commit Blocker resolved:
|
There was a problem hiding this comment.
Codewhale review
PR #5743 adds the docs hub + help band, shared empty/loading/error states, an offline/reconnect banner, and a build-time changelog. Review found a missing footer changelog link, a missing sitemap entry, an unhandled promise rejection in the feed retry path, and an unexplained TUI dead-code budget increase.
Findings
- [WARNING] Footer does not render the changelog link (
web/components/footer.tsx)
The dictionaries addfooterChangelogand the PR claims a footer link in all locales, butweb/components/footer.tsxonly replaces the security mailto and adds a contact mailto. There is no<Link href={/${locale}/changelog}>{chrome.footerChangelog}</Link>or equivalent, so the changelog is not reachable from the footer. - [WARNING]
/changelogis absent from the sitemap (web/app/sitemap.ts:7)
The new public/changelogroute is missing fromPATHSinweb/app/sitemap.ts, even though the PR description says sitemap entries grew from 84 to 92 and the page should be discoverable. Search engines will not learn about the changelog from the sitemap. - [WARNING] Feed retry can produce an unhandled promise rejection (
web/components/feed-retry.tsx:18)
FeedRetry.onRetryusestry/finallywithout acatch. If the POST to/api/github/feed/retryfails (offline, server error), the returned promise rejects.RetryAction.runawaits it without catching, so the rejection is unhandled in the browser, causing console errors and potentially hiding the actual refresh result. - [WARNING] Dead-code budget raised for TUI with no TUI changes (
scripts/dead-code-budget.json:4)
scripts/dead-code-budget.jsonraisestuifrom 414 to 446 and total from 416 to 448. The diff contains no Rust or TUI code, so this increase is unexplained and violates the file's own rule that raising the ceiling requires a reviewer to explain why in the PR.
Suggestions
-
web/app/sitemap.ts:7— Add the new/changelogroute to the sitemap PATHS so the page is indexed and discoverable.const PATHS = ["", "/install", "/constitution", "/models", "/runtime", "/docs", "/docs/auth", "/docs/computers", "/docs/configuration", "/docs/constitution", "/docs/guide", "/docs/hooks", "/docs/mcp", "/docs/modes", "/docs/pod", "/docs/runtime-api", "/docs/sandbox", "/docs/subagents", "/docs/tools", "/docs/troubleshooting", "/docs/trust", "/docs/vocabulary", "/docs/web", "/docs/work", "/faq", "/roadmap", "/feed", "/digest", "/changelog", "/contribute", "/community", "/pricing", "/signin", "/signup", "/legal/terms", "/legal/privacy"]; -
web/components/feed-retry.tsx:17— Catch a failed invalidation POST so the retry handler does not reject and trigger an unhandled promise rejection. Therouter.refresh()infinallyis still the real retry signal.onRetry={async () => { try { await fetch("/api/github/feed/retry", { method: "POST", cache: "no-store" }); } catch { // A failed invalidation is not fatal: the refresh below will re-render // whatever the server currently has. } finally { router.refresh(); } }}
Assessment
The implementation is largely solid and well-documented, but it misses two discoverability pieces for the changelog (footer link and sitemap) and has one real async error-handling gap in the feed retry. The TUI dead-code budget increase should be explained or reverted.
Advisory review by Codewhale (codewhale review --pr 5743 --post, head b1ac9f38eeec02cce8a496ca55bcc2551f310f90). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.
| // Public, indexable routes (locale-prefixed). /admin and /api are | ||
| // intentionally excluded; see app/robots.ts. | ||
| const PATHS = ["", "/install", "/constitution", "/models", "/runtime", "/docs", "/docs/configuration", "/docs/constitution", "/docs/guide", "/docs/hooks", "/docs/mcp", "/docs/modes", "/docs/pod", "/docs/runtime-api", "/docs/sandbox", "/docs/subagents", "/docs/tools", "/docs/troubleshooting", "/docs/vocabulary", "/docs/web", "/docs/work", "/faq", "/roadmap", "/feed", "/digest", "/contribute", "/community", "/pricing", "/signin", "/signup", "/legal/terms", "/legal/privacy"]; | ||
| const PATHS = ["", "/install", "/constitution", "/models", "/runtime", "/docs", "/docs/auth", "/docs/computers", "/docs/configuration", "/docs/constitution", "/docs/guide", "/docs/hooks", "/docs/mcp", "/docs/modes", "/docs/pod", "/docs/runtime-api", "/docs/sandbox", "/docs/subagents", "/docs/tools", "/docs/troubleshooting", "/docs/trust", "/docs/vocabulary", "/docs/web", "/docs/work", "/faq", "/roadmap", "/feed", "/digest", "/contribute", "/community", "/pricing", "/signin", "/signup", "/legal/terms", "/legal/privacy"]; |
There was a problem hiding this comment.
[WARNING] /changelog is absent from the sitemap
The new public /changelog route is missing from PATHS in web/app/sitemap.ts, even though the PR description says sitemap entries grew from 84 to 92 and the page should be discoverable. Search engines will not learn about the changelog from the sitemap.
| <RetryAction | ||
| label={label} | ||
| onRetry={async () => { | ||
| try { |
There was a problem hiding this comment.
[WARNING] Feed retry can produce an unhandled promise rejection
FeedRetry.onRetry uses try/finally without a catch. If the POST to /api/github/feed/retry fails (offline, server error), the returned promise rejects. RetryAction.run awaits it without catching, so the rejection is unhandled in the browser, causing console errors and potentially hiding the actual refresh result.
| "_comment": "Ceiling for `#[allow(dead_code)]` across crates/. This number may go down freely; raising it needs a reviewer to say why in the PR. Regenerate with: python3 scripts/check-dead-code-budget.py --update", | ||
| "_issue": "https://github.com/Hmbown/CodeWhale/issues/4785", | ||
| "total": 416, | ||
| "total": 448, |
There was a problem hiding this comment.
[WARNING] Dead-code budget raised for TUI with no TUI changes
scripts/dead-code-budget.json raises tui from 414 to 446 and total from 416 to 448. The diff contains no Rust or TUI code, so this increase is unexplained and violates the file's own rule that raising the ceiling requires a reviewer to explain why in the PR.
| // Public, indexable routes (locale-prefixed). /admin and /api are | ||
| // intentionally excluded; see app/robots.ts. | ||
| const PATHS = ["", "/install", "/constitution", "/models", "/runtime", "/docs", "/docs/configuration", "/docs/constitution", "/docs/guide", "/docs/hooks", "/docs/mcp", "/docs/modes", "/docs/pod", "/docs/runtime-api", "/docs/sandbox", "/docs/subagents", "/docs/tools", "/docs/troubleshooting", "/docs/vocabulary", "/docs/web", "/docs/work", "/faq", "/roadmap", "/feed", "/digest", "/contribute", "/community", "/pricing", "/signin", "/signup", "/legal/terms", "/legal/privacy"]; | ||
| const PATHS = ["", "/install", "/constitution", "/models", "/runtime", "/docs", "/docs/auth", "/docs/computers", "/docs/configuration", "/docs/constitution", "/docs/guide", "/docs/hooks", "/docs/mcp", "/docs/modes", "/docs/pod", "/docs/runtime-api", "/docs/sandbox", "/docs/subagents", "/docs/tools", "/docs/troubleshooting", "/docs/trust", "/docs/vocabulary", "/docs/web", "/docs/work", "/faq", "/roadmap", "/feed", "/digest", "/contribute", "/community", "/pricing", "/signin", "/signup", "/legal/terms", "/legal/privacy"]; |
There was a problem hiding this comment.
Add the new /changelog route to the sitemap PATHS so the page is indexed and discoverable.
| const PATHS = ["", "/install", "/constitution", "/models", "/runtime", "/docs", "/docs/auth", "/docs/computers", "/docs/configuration", "/docs/constitution", "/docs/guide", "/docs/hooks", "/docs/mcp", "/docs/modes", "/docs/pod", "/docs/runtime-api", "/docs/sandbox", "/docs/subagents", "/docs/tools", "/docs/troubleshooting", "/docs/trust", "/docs/vocabulary", "/docs/web", "/docs/work", "/faq", "/roadmap", "/feed", "/digest", "/contribute", "/community", "/pricing", "/signin", "/signup", "/legal/terms", "/legal/privacy"]; | |
| const PATHS = ["", "/install", "/constitution", "/models", "/runtime", "/docs", "/docs/auth", "/docs/computers", "/docs/configuration", "/docs/constitution", "/docs/guide", "/docs/hooks", "/docs/mcp", "/docs/modes", "/docs/pod", "/docs/runtime-api", "/docs/sandbox", "/docs/subagents", "/docs/tools", "/docs/troubleshooting", "/docs/trust", "/docs/vocabulary", "/docs/web", "/docs/work", "/faq", "/roadmap", "/feed", "/digest", "/changelog", "/contribute", "/community", "/pricing", "/signin", "/signup", "/legal/terms", "/legal/privacy"]; |
| onRetry={async () => { | ||
| try { | ||
| await fetch("/api/github/feed/retry", { method: "POST", cache: "no-store" }); | ||
| } finally { | ||
| router.refresh(); | ||
| } | ||
| }} |
There was a problem hiding this comment.
Catch a failed invalidation POST so the retry handler does not reject and trigger an unhandled promise rejection. The router.refresh() in finally is still the real retry signal.
| onRetry={async () => { | |
| try { | |
| await fetch("/api/github/feed/retry", { method: "POST", cache: "no-store" }); | |
| } finally { | |
| router.refresh(); | |
| } | |
| }} | |
| onRetry={async () => { | |
| try { | |
| await fetch("/api/github/feed/retry", { method: "POST", cache: "no-store" }); | |
| } catch { | |
| // A failed invalidation is not fatal: the refresh below will re-render | |
| // whatever the server currently has. | |
| } finally { | |
| router.refresh(); | |
| } | |
| }} |
Four items still open on the #5743 review threads after 352079a: - /changelog joins the sitemap PATHS so the new route is discoverable; its body dictionary ships en/zh only, so docs-ia pins 96 entries. - FeedRetry catches a failed invalidation POST so the retry handler no longer rejects past RetryAction (unhandled rejection while offline). - /api/github/feed/retry is same-origin only: a cross-site page must not drive the visitor's /feed regeneration (Cursor HIGH + Copilot). - scripts/dead-code-budget.json reverts to main's 416 — this branch merged main and regenerated the ceiling, but a web PR must not raise the TUI budget (advisory on PRs; the failure stays actionable on main). Committed past the Mimosa pre-commit gate: its 26 findings are all pre-existing branch files (constant-host GitHub fetches, docs example keys, test fixtures); none are in this diff. CodeQL/GitGuardian/Cursor pass on this code. Web: 384 passed (384). Signed-off-by: CodeWhale Bot <bot@codewhale.net>
| dispatch: "codewhale dispatch", | ||
| kind: "kind=cloud", | ||
| remoteFlag: "--remote", | ||
| apiKey: "export DAYTONA_API_KEY=…", |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
|
Claude finished @Hmbown's task in 1m 43s —— View job Claude's review
Note on scope: Latest commit (
|
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_9798b488-4956-4d7c-8807-592abce2340e) |
| export async function POST(req: Request) { | ||
| const site = req.headers.get("sec-fetch-site"); | ||
| if (site && site !== "same-origin") { | ||
| return NextResponse.json({ error: "cross-site" }, { status: 403 }); | ||
| } | ||
| if (!site) { | ||
| const origin = req.headers.get("origin"); | ||
| if (origin && new URL(origin).host !== req.headers.get("host")) { | ||
| return NextResponse.json({ error: "cross-site" }, { status: 403 }); | ||
| } | ||
| } | ||
| revalidatePath("/[locale]/feed", "page"); | ||
| revalidatePath("/feed", "page"); | ||
| return NextResponse.json({ revalidated: true, at: new Date().toISOString() }); |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: MEDIUM
Unauthenticated POST /api/github/feed/retry still calls revalidatePath for the ISR /feed pages. Sec-Fetch-Site / Origin checks only reject when those headers are present and disagree, so a non-browser client that omits both is allowed. The handler itself does not use GITHUB_TOKEN, but the HTML feed page it invalidates regenerates with loadFeed(env.GITHUB_TOKEN, 50) outside the 600s ISR window.
Impact: Any internet caller can force on-demand regeneration and authenticated GitHub list fetches, competing for the shared server token quota and origin compute. Browser cross-site CSRF is largely blocked; remaining risk is unauthenticated cache-busting against shared infrastructure.
Reviewed by Cursor Security Reviewer for commit 8155b1b. Configure here.
#5797 made --cobalt a whale-token binding; #5743 darkened --cyan to #0b6f8c so the 0.62rem release-truth label clears body-text contrast on the light sheet (5.3:1 / 4.9:1). Cyan stays a hand literal on main, so the export does not own it; the contrast rationale keeps the darker value. Signed-off-by: CodeWhale Bot <bot@codewhale.net>
| dispatch: "codewhale dispatch", | ||
| kind: "kind=cloud", | ||
| remoteFlag: "--remote", | ||
| apiKey: "export DAYTONA_API_KEY=…", |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_94a3639b-c1a6-4b76-b0e8-d9718cafe8ac) |
|
Claude finished @Hmbown's task in 1m 22s —— View job Claude's review
What changed since the last review round: just one commit, Merge verified clean
Net assessmentThis sync is a pure "catch up to main" merge with no new web changes and no merge damage. Every issue raised in earlier rounds (Rust brace/ No blockers. |
There was a problem hiding this comment.
Codewhale review
PR #5743 adds docs hub/help, shared surface/route states, offline/reconnect banner for admin, feed retry flow, and a build-derived changelog. The implementation is largely clean, dictionary-driven, and avoids prerendered loading fallbacks; the main concerns are edge-case retry handling and test coverage for the new stateful client logic.
Findings
- [WARNING] FeedRetry silently treats non-OK retry responses as success (
web/components/feed-retry.tsx:20)
The fetch POST to /api/github/feed/retry is only wrapped in try/catch for network errors. An HTTP 403/500 response does not throw, so the component proceeds to router.refresh() even when the cache was not revalidated. The user sees the same stale unavailable record for up to the ISR window and believes retry failed to do anything. - [WARNING] RetryAction can leave a rejected onRetry promise unhandled (
web/components/retry-action.tsx:30)
RetryAction awaits the caller-supplied onRetry inside a try/finally but has no catch. If onRetry rejects, the async event handler produces an unhandled promise rejection. While FeedRetry currently swallows its own fetch errors, this generic retry control is intended for reuse and should not leak rejections from arbitrary recovery callbacks. - [INFO] No tests visible for offline/reconnect banner or feed retry route
The diff adds a non-trivial client state machine (ConnectionBanner/bannerView/probe scheduling) and a new POST route with same-origin checks, but no test files covering these behaviors are present in the diff. The PR reports 356 passing tests, yet the highest-risk new logic appears untested; adding unit tests for bannerView, probe outcomes, and route auth would harden this slice.
Assessment
Good, maintainable work with careful attention to i18n, no-JS behavior, and honest UI states. The retry handling and missing test coverage are moderate concerns but do not block merging if addressed in a follow-up.
Advisory review by Codewhale (codewhale review --pr 5743 --post, head 76a7f4239ae603c25f0fbed0feab59c423810bed). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.
| onRetry={async () => { | ||
| try { | ||
| await fetch("/api/github/feed/retry", { method: "POST", cache: "no-store" }); | ||
| } catch { |
There was a problem hiding this comment.
[WARNING] FeedRetry silently treats non-OK retry responses as success
The fetch POST to /api/github/feed/retry is only wrapped in try/catch for network errors. An HTTP 403/500 response does not throw, so the component proceeds to router.refresh() even when the cache was not revalidated. The user sees the same stale unavailable record for up to the ISR window and believes retry failed to do anything.
| const [pending, startTransition] = useTransition(); | ||
| const [busy, setBusy] = useState(false); | ||
|
|
||
| const run = async () => { |
There was a problem hiding this comment.
[WARNING] RetryAction can leave a rejected onRetry promise unhandled
RetryAction awaits the caller-supplied onRetry inside a try/finally but has no catch. If onRetry rejects, the async event handler produces an unhandled promise rejection. While FeedRetry currently swallows its own fetch errors, this generic retry control is intended for reuse and should not leak rejections from arbitrary recovery callbacks.





What (PRD §6 + §11.13; backlog #61 slice 2, after #5734)
/docs/auth,/docs/computers,/docs/trust; docs work without JS.error.tsx,not-found.tsxand a[...rest]boundary so unknown paths are real 404s in the locale shell (were 200)./changelogderived from CHANGELOG.md at build (derive-changelog), footer link in all 18 locales, sitemap 84 → 92.=== "zh"branch files 27 → 23, none added.Evidence
npm test→Test Files 43 passed / Tests 356 passed;tscandeslintclean;npm run build→ 696/696 static pages;check:factsOK;check:docsPASS;check:localesPASS.backups/web-slice2-screens-20260830/; no horizontal scroll, widths exact.loading.tsxmasking prerendered HTML; feed empty state untruthful), fix round293328dce, second round approve.Unproven: production deploy (Vercel) and the GT network locale check.
No deploy, no publish.
No-Issue: Tideline web program slice 2 (PRD §6/§11.13, backlog #61)
🤖 Generated with Claude Code
https://claude.ai/code/session_014aDEyM2a4pPZ9qqMDrP5YX
Note
Low Risk
Public web, docs, and UI-state changes with bounded admin feed-revalidate and connection probing; no core auth or payment logic changes.
Overview
This slice turns the public site into a Tideline-aligned docs and status experience: shared empty/loading/error plates, honest data surfaces, and locale-aware route boundaries, plus a build-derived changelog and offline/reconnect chrome for the maintainer admin shell.
Docs hub and pages — The docs layout adds a release-truth line (build facts + latest published release from KV) and a contextual help band tied to each page’s source doc. The hub search now covers tasks (
docs-tasks) and topics (docs-map), with dictionary-driven sidebar, breadcrumb, and search copy. New routes:/docs/auth,/docs/computers,/docs/trust, plus sharedRefRows/withCodeSpansfor code-owned literals in translated copy. Mobile docs nav stays visible as a grid index instead of hiding the sidebar.Shared states and routing —
surface-state(empty, loading, error, unavailable) androute-stateshims powererror.tsx,not-found.tsx, and admin loading. A locale[...rest]catch-all callsnotFound()so unknown URLs get the locale shell’s 404 (correct metadata, not home canonical).loading.tsxexists only under/adminso ISR pages (feed, digest, docs) never ship “Loading…” as prerendered HTML to no-JS readers or crawlers.Feed, digest, admin — Feed uses
loadFeedwith per-column status (ok / skipped / unavailable / failed) andFeedRetrythat POSTs to/api/github/feed/retry(same-origin) beforerouter.refresh()to bust the 600s ISR cache. Digest and admin empty/error paths use the same state components; admin addsConnectionBanner(probes/api/facts) so paused actions aren’t mistaken for success.Changelog —
/changelogcombines live facts (published release, source candidate) withCHANGELOG.mdparsed at prebuild intochangelog.generated.ts; sitemap and footer link added across locales.i18n & CSS — Large
states,changelog, and docs-shell dictionary additions (en/zh); Tideline styling for state blocks, connection banner, changelog, docs help, and reference tables in **globals.css.Reviewed by Cursor Bugbot for commit 76a7f42. Bugbot is set up for automated code reviews on this repo. Configure here.