Skip to content

fix(canvas): show unavailable state instead of blank Settings panel - #2275

Merged
2witstudios merged 4 commits into
masterfrom
pu/canvas-follow-up
Jul 30, 2026
Merged

2witstudios merged 4 commits into
masterfrom
pu/canvas-follow-up

Conversation

@2witstudios

@2witstudios 2witstudios commented Jul 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes an unresolved review finding from #2250 (comment) that was left open when that PR merged.

  • PublishControls returns null when publishing is unconfigured, disabled, or unavailable to a read-only viewer (403 from the publish-status endpoint). In the content header that's harmless — one fewer button among several others — but PR Move canvas settings into tab bar (View/Code/Forms/Settings) #2250 moved canvas publish controls into a dedicated, always-selectable "Settings" tab, where PublishControls is the sole content. Selecting Settings in the unavailable case produced a blank panel with no explanation and no way to tell what happened.
  • Added a variant?: 'header' | 'panel' prop to PublishControls: the 'header' variant (default) keeps the existing silent null behavior; the 'panel' variant (used by CanvasPageView's Settings tab) renders an explanatory message instead.
  • Review follow-up (both CodeRabbit and Codex independently flagged the same issue): the original fix collapsed transient load failures (5xx, network errors) into the same message as a genuine, durable "publishing unavailable" signal. Added a separate hasLoadError state so the panel now distinguishes:
    • Genuine unavailability (a definitive 403, or a successful response explicitly reporting available: false) → "Publishing isn't available for this page."
    • A transient load failure (5xx / network error) → "Couldn't load publishing status. Try again shortly."
  • Self-review pass: renamed loadError → hasLoadError to match this file's existing is*/has* boolean naming (isLoading, isBusy, isStale), and rewrote the new test file to use the given/should/actual/expected assert() helper from stores/__tests__/riteway, matching the established convention in the sibling ShareDialog.test.tsx (same directory) rather than raw expect() assertions.
  • /simplify pass (4 parallel review agents — reuse, simplification, efficiency, altitude): reuse/efficiency/altitude came back clean (the variant prop and local 403-classification logic match established codebase idioms — checked against PageWebhooksDialog.tsx and others). Simplification flagged that hasLoadError was a second, easy-to-desync useState alongside the PublishState object — folded it into PublishState itself so every fetch-outcome branch sets both fields atomically in one setState call, added an EMPTY_STATE constant to replace a 3x-duplicated empty-state literal, and consolidated the test file's three response-factory functions into one parametrized makeStatusResponse(overrides), matching the precedent in BackupDiffPreview.test.tsx. No behavior change — same 16 tests pass.

Test plan

  • bun run typecheck (web) — passes
  • bun run lint (web) — passes (only pre-existing, unrelated warnings)
  • bun run knip:check — no new issues
  • New unit tests in PublishControls.test.tsx (6 cases: durable-unavailable vs load-error, for both variants) — all pass
  • Manually verify in the dev app:
    • Read-only viewer on a canvas page → Settings tab shows the durable unavailable message, not a blank panel
    • Publishing disabled/unconfigured (CANVAS_PUBLISHING_DISABLED=true or no PUBLISH_BUCKET) → same durable message
    • A transient 5xx/network blip while loading status → shows the distinct "couldn't load" message, not the durable one
    • Editor with publishing available → Settings tab still shows the normal Publish/published controls, unchanged
    • Non-canvas publishable page (e.g. Document) with publishing unavailable → header still shows nothing extra, unchanged

🤖 Generated with Claude Code

https://claude.ai/code/session_01FgEDGrDoiYZnXwsY5Jg53g

PublishControls returns null when publishing is unconfigured, disabled,
or unavailable to a read-only viewer. In the content header that was
harmless (one fewer button among several), but the canvas Settings tab
renders it as the sole content — selecting the tab produced a blank
panel with no explanation.

Add a variant prop ('header' | 'panel') so the panel usage renders an
explanatory message instead of null, while the header usage keeps its
existing silent behavior.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

PublishControls now supports header and panel variants. Header usage remains silent when publishing is unavailable, while the Settings panel displays an explanatory muted message.

Changes

Publish control display variants

Layer / File(s) Summary
Variant-based unavailable publishing display
apps/web/src/components/layout/middle-content/content-header/PublishControls.tsx, apps/web/src/components/layout/middle-content/page-views/canvas/CanvasPageView.tsx
PublishControls accepts an optional variant, defaults to header, preserves hidden rendering for unavailable header controls, renders explanatory panel text for panel, and the Settings tab passes variant="panel".

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main user-facing change: the Settings panel now shows an unavailable state instead of staying blank.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/canvas-follow-up

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6ccb924c0a

ℹ️ 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".

Comment on lines +220 to +224
if (!state.available) {
if (variant === 'panel') {
return (
<p className="text-sm text-muted-foreground">
Publishing isn&apos;t available for this page.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish fetch failures from unavailable publishing

When the status request fails because of a network error or any non-OK response other than the expected permission case (for example, the route's 500 response), the effect also sets available to false. The panel therefore permanently reports that publishing is unavailable rather than indicating that its status could not be loaded or offering a retry, misleading editors during transient outages until the component is remounted. Track request failures separately from a successful { available: false } response.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d7babae: added a separate loadError state that's only set on a genuine load failure (non-403 non-ok response, or a caught network error). The panel variant now shows a distinct "Couldn't load publishing status. Try again shortly." message in that case, and reserves "Publishing isn't available for this page." for a definitive signal (403 permission-denied, or a successful response explicitly reporting available: false). Header variant behavior is unchanged. Added test coverage for both branches in both variants.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/web/src/components/layout/middle-content/content-header/PublishControls.tsx`:
- Around line 216-227: Update the state-loading flow in PublishControls so
request failures and caught network errors are tracked separately from a
successful response reporting available === false. In the variant === 'panel'
branch of the !state.available handling, show the “Publishing isn’t available”
message only for an explicitly successful unavailable response; preserve the
existing behavior for genuine availability results and avoid presenting failures
as permanent unavailability.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a0966ae6-8a1a-452e-9956-295cd27d0a68

📥 Commits

Reviewing files that changed from the base of the PR and between 900bc25 and 6ccb924.

📒 Files selected for processing (2)
  • apps/web/src/components/layout/middle-content/content-header/PublishControls.tsx
  • apps/web/src/components/layout/middle-content/page-views/canvas/CanvasPageView.tsx

…ility

Both Codex and CodeRabbit review flagged the same issue: PublishControls'
status effect collapsed every non-ok response and network error into the
same `available: false` state as a genuine "publishing not available"
signal (403 for no permission, or a successful response explicitly
reporting available: false). In the panel variant that meant a transient
5xx or network blip would render the same durable-sounding "isn't
available" message.

Track load failures (`loadError`) separately from a definitive 403 or a
successful available:false response, and show a distinct "couldn't load"
message for the former in the panel variant. Header variant behavior is
unchanged (stays silent either way).

Also adds a test file covering both branches for both variants.
Self-review pass:
- Rename loadError/setLoadError -> hasLoadError/setHasLoadError to match
  this file's existing is*/has* boolean naming (isLoading, isBusy,
  isStale).
- Rewrite PublishControls.test.tsx to use the given/should/actual/expected
  assert() helper from stores/__tests__/riteway, matching the established
  convention in the sibling ShareDialog.test.tsx (same directory) rather
  than raw expect() assertions.

No behavior change.
/simplify pass (4 parallel review agents: reuse, simplification,
efficiency, altitude) on PR #2275's diff. Reuse/efficiency/altitude came
back clean; simplification flagged two worthwhile fixes:

- hasLoadError was a separate useState alongside the PublishState object,
  requiring every fetch-outcome branch to remember two setState calls
  that had to stay in sync by hand. Folded it into PublishState instead,
  so one setState call per branch keeps `available` and `hasLoadError`
  atomically consistent. Also drops a reset call that was already dead
  code (isLoading gates the render before either field is ever read).
- Added an EMPTY_STATE constant to replace the same empty-state object
  literal that was duplicated across 3 call sites.
- Consolidated the test file's three near-identical response factories
  (make403Response/make500Response/makeUnavailableResponse) into one
  parametrized makeStatusResponse(overrides), matching the precedent in
  BackupDiffPreview.test.tsx.

No behavior change — same 16 tests pass unmodified in assertions.
@2witstudios
2witstudios merged commit 65154e8 into master Jul 30, 2026
3 checks passed
@2witstudios
2witstudios deleted the pu/canvas-follow-up branch July 31, 2026 02:47
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