Skip to content

feat(web): add active publisher context#2744

Open
vyctorbrzezowski wants to merge 84 commits into
openclaw:mainfrom
vyctorbrzezowski:brzezowski/active-publisher-selector
Open

feat(web): add active publisher context#2744
vyctorbrzezowski wants to merge 84 commits into
openclaw:mainfrom
vyctorbrzezowski:brzezowski/active-publisher-selector

Conversation

@vyctorbrzezowski

@vyctorbrzezowski vyctorbrzezowski commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Proposal

This PR adds a global active publisher context to ClawHub.

Until this change, ClawHub had enough publisher/org primitives in the data model, but the product UI still treated many signed-in actions as if they were implicitly personal. That made organization work feel patched on: users could belong to orgs, but the header, dashboard, settings, and publishing surfaces did not all share one obvious answer to “who am I acting as right now?”

This PR makes that answer explicit and consistent:

  • A signed-in user has one active publisher context at a time.
  • The active publisher can be their personal publisher or an organization they belong to.
  • Publisher-scoped actions inherit that active context.
  • Account/app actions remain tied to the signed-in user.

The goal is not to turn ClawHub into a workspace app with a heavy org switcher everywhere. The goal is a small, durable context model that lets users publish and manage work as either themselves or an organization without mixing account identity, publisher identity, and app-level controls.

Product model

The PR separates three concepts that were previously easy to blur:

Concept Meaning Examples
Signed-in account The authenticated user session sign out, appearance, stars
Active publisher The current publishing/profile context personal publisher, organization publisher
Publisher-scoped actions Actions that should apply to the active publisher dashboard, profile, settings, publish skill/plugin

In practice:

  • Dashboard follows the active publisher.
  • Profile / Org profile follows the active publisher.
  • Settings follows the active publisher.
  • Skill and plugin publishing follows the active publisher unless an explicit ownerHandle URL says otherwise.
  • Stars stays personal/account-scoped.
  • Appearance stays app-scoped.
  • Sign out stays account-scoped.

Header and global menu

The header now exposes the current publisher as the primary signed-in control.

For a personal publisher, the menu shows the personal publisher identity and the personal actions:

  • Dashboard
  • Profile
  • Stars
  • Settings
  • Appearance
  • Sign out

If the user has no organization yet, Create organization appears directly under their identity so the next step is discoverable without taking over the menu.

For an organization publisher, the menu changes the publisher-scoped actions to organization language:

  • Dashboard
  • Org profile
  • Settings

Account/app actions stay separated below the scoped actions. Stars is intentionally not shown in the organization context because stars belong to the signed-in account, not to the active org.

The switcher is nested inside the current publisher row instead of being a second competing dropdown. Selecting the publisher row opens a context switch panel with personal/org options and Create organization. This keeps the main menu compact while still making the active context clear.

Settings behavior

Settings are now context-aware.

When the active context is personal, /settings edits the signed-in user/personal publisher surface:

  • Profile
  • Appearance
  • Organizations
  • API tokens
  • Danger zone

When the active context is an organization, /settings edits that organization:

  • Profile
  • Members, when the user can manage the org
  • GitHub Skill Sync, when the active org is eligible/configurable
  • Danger zone, when the user is allowed to delete the org

This avoids mixing user profile editing and org profile editing in the same visual state. The page title/subtitle and sidebar reflect the active settings scope.

Role gating is preserved:

  • Owners/admins can manage organization settings and members.
  • Publisher-only members can switch into an org context but do not get management actions.
  • Org deletion remains owner-only.
  • GitHub source management remains limited to eligible manageable official publishers.

Organizations settings page

The personal settings area includes an Organizations view that lists the orgs available to the account.

Each org card shows:

  • organization avatar/icon
  • display name
  • handle
  • user role badge
  • skill count
  • plugin count
  • View profile
  • Manage or Switch, depending on permissions

The layout is intentionally card-based and supports two orgs per row on wide screens while staying compact enough for the common case where a user has one org.

Publish skill/plugin flows

The skill and plugin publish pages now show the current publishing owner at the top of the upload card.

The strip is intentionally lightweight:

  • avatar/icon
  • publisher display name
  • muted handle on larger screens
  • Switch publisher action when another context is available

The selector updates the ownerHandle URL when switching context, so publishing state is shareable, reload-safe, and not only stored in client memory.

Important behavior preserved:

  • An explicit ownerHandle in the URL wins when opening the page.
  • Existing skill update flows keep the existing owner by default.
  • Owner migration stays opt-in instead of happening accidentally during an update.
  • Switching publisher on the publish page updates both the visible “publishing as” context and the URL.

Dashboard behavior

Dashboard queries now follow active publisher scope without breaking legacy personal ownership.

For org publishers, the dashboard queries by ownerPublisherId.

For personal publishers, the dashboard continues querying by ownerUserId. This matters because local/legacy personal publishers can be synthesized or compatibility-backed; treating those as real publisher document IDs can hide existing personal content. This PR keeps personal dashboards on the user-owner path while allowing org dashboards to use publisher ownership.

Backend/data changes

This PR adds a lightweight publisher membership query for the global context provider.

The existing richer publishers.listMine query is still useful for settings pages that need profile/status/count data. The app-level context provider does not need that full payload, so it now uses a narrower query that returns only publisher identity and role data.

That avoids putting a heavy “all published items” subscription around the entire app shell.

The lightweight membership query still handles:

  • signed-in user filtering
  • deleted/deactivated user guards
  • active publisher filtering
  • user-kind publisher ownership checks
  • legacy personal publisher fallback through personalPublisherId

Loading and deep-link behavior

The settings page now avoids normalizing org-only deep links before publisher memberships finish loading.

For example, /settings?view=members should not be immediately rewritten to /settings?view=profile just because memberships have not arrived yet. The page can show a skeleton while loading, but it should preserve the requested view until it knows whether the active publisher is an org and whether the requested view is valid.

This PR adds that loading guard and regression coverage.

What intentionally does not change

This PR does not change the underlying authorization rules for publishing or org management.

It also does not make stars organization-scoped. Stars remain personal/account-level.

It does not make appearance publisher-scoped. Appearance remains an app/user preference.

It does not make update publishing migrate owners automatically. Migration must remain explicit.

It does not remove direct URL ownership. ownerHandle remains the shareable way to open publish flows in a specific owner context.

Evidence

Personal account menu

The personal menu keeps account/app actions separate from publisher-scoped actions. Create organization is promoted only when there is no org yet, and Stars appears only in the personal context.

Personal account menu with Create organization, Dashboard, Profile, Stars, Settings, Appearance, and Sign out

Header context switch flow

This video shows the header interaction model: open the current publisher menu, use the nested publisher switcher, switch between personal/org contexts, and return to scoped actions for the selected publisher.

Open video directly

Organizations management in personal settings

The Organizations view lists available orgs from the signed-in account, with profile access, management access, role indication, and lightweight skill/plugin metadata.

Organizations settings view with two organization cards, role badges, counts, View profile, and Manage actions

Personal settings profile

When the active context is personal, Settings edits the user/personal profile rather than an organization.

Personal settings profile view for Local Owner

Organization settings profile

When an org is active, Settings switches to the organization profile and exposes org-specific navigation such as Members and Danger zone.

Organization settings profile view for Openclaw with Members and Danger zone navigation

Publish page context switcher

The skill publish page shows the current publishing owner and allows switching publisher from the publishing surface itself. The menu uses neutral selection styling with a checkmark and keeps personal/org options distinct.

Publish skill page with Switch publisher dropdown showing personal and organization publishers

Publish page owner strip

The publish owner strip is attached to the upload card and shows the active publishing owner without extra explanatory copy. It keeps the page focused on the upload task while making the publishing target visible before the user drops a folder.

Publish skill page showing compact owner strip above the dropzone

Testing

  • bunx vitest run src/routes/-settings.test.tsx src/lib/activePublisher.test.tsx src/routes/-dashboard.test.tsx src/__tests__/header.test.tsx src/__tests__/skills-publish-route.test.tsx src/__tests__/plugins-publish-route.test.tsx
  • bun run ci:static
  • bunx tsc --noEmit
  • bun run ci:types-build
  • bun run ci:unit
  • .agents/skills/autoreview/scripts/autoreview --mode branch --base upstream/main --engine opencode --model opencode-go/kimi-k2.7-code --thinking medium --no-web-search

Review notes

A second-model review with Kimi K2.7 Code via Opencode found no accepted/actionable findings after the final rebase.

The review specifically checked the product goal and the highest-risk paths:

  • role-gated org settings
  • publish owner URL synchronization
  • explicit ownerHandle preservation
  • loading/deep-link behavior
  • legacy/synthetic personal publisher fallback
  • global context query cost
  • personal vs organization dashboard ownership
  • account-scoped vs publisher-scoped menu actions

This reverts commit 30b0adbbd7c72de7040d5555c8973becbb120726.
@vercel

vercel Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

@vyctorbrzezowski is attempting to deploy a commit to the Amantus Machina Team on Vercel.

A member of the Team first needs to authorize it.

@vyctorbrzezowski

Copy link
Copy Markdown
Contributor Author

@clawsweeper review

@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@vyctorbrzezowski vyctorbrzezowski marked this pull request as ready for review June 19, 2026 01:09
@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal backlog priority with limited blast radius. labels Jun 19, 2026
@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed June 18, 2026, 9:42 PM ET / 01:42 UTC.

Summary
Adds a global active publisher context across the app shell, dashboard, settings, and skill/plugin publish flows, with a new lightweight Convex membership query, specs, styles, and tests.

Reproducibility: yes. for the PR finding: the live CI run reproduces it across local-auth shards, and the source shows the trigger now prefers displayName over the @handle. This is not a current-main bug reproduction; it is a PR regression check.

Review metrics: 2 noteworthy metrics.

  • Diff Size: +3601/-1350 across 19 files. This is a broad app-shell, settings, publishing, backend-query, spec, style, and test change rather than a narrow UI patch.
  • Local-Auth Failures: 10 shards fail at header .user-trigger. The same changed header identity behavior blocks signed-in browser validation across otherwise unrelated local-auth flows.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Restore visible or otherwise testable @handle identity in the header trigger.
  • [P2] Rerun the focused header tests and local-auth suite after the header fix.
  • Have maintainers confirm the global active publisher UX direction once CI is clean.

Risk before merge

  • [P1] The PR currently causes all local-auth shards that rely on the signed-in header trigger to fail, so the merge would land with broken local signed-in flow validation.
  • [P1] This is a product-level feature that changes global signed-in navigation, settings scope, and publish defaults; after the mechanical blocker is fixed, maintainers still need to accept that active publisher UX direction.

Maintainer options:

  1. Decide the mitigation before merge
    Keep the active publisher model only if the header keeps a stable visible or accessible @handle for the active publisher, local-auth passes, and maintainers accept the global context UX.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] The blocking defect is a narrow header identity repair with clear failing CI evidence, so a repair worker can attempt it before maintainer product review.

Security
Cleared: No concrete security or supply-chain regression was found; the new membership query is auth-derived and the active publisher context is documented as a UI default rather than an authorization boundary.

Review findings

  • [P2] Keep the publisher handle visible in the header trigger — src/components/Header.tsx:151-153
Review details

Best possible solution:

Keep the active publisher model only if the header keeps a stable visible or accessible @handle for the active publisher, local-auth passes, and maintainers accept the global context UX.

Do we have a high-confidence way to reproduce the issue?

Yes for the PR finding: the live CI run reproduces it across local-auth shards, and the source shows the trigger now prefers displayName over the @handle. This is not a current-main bug reproduction; it is a PR regression check.

Is this the best way to solve the issue?

No, not merge-ready as-is. The active publisher approach is plausible, but the header trigger must preserve stable publisher identity and local-auth validation before maintainers decide the product direction.

Full review comments:

  • [P2] Keep the publisher handle visible in the header trigger — src/components/Header.tsx:151-153
    The trigger title now prefers displayName, so local-auth shards see text like Local Admin or Local Owner instead of an @handle and fail in expectLocalPersonaActive. The active publisher handle is the stable identity this UI needs to expose; render an @handle in the trigger text or accessible identity before updating tests.
    Confidence: 0.9

Overall correctness: patch is incorrect
Overall confidence: 0.86

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 6bbdec2a4ef8.

Label changes

Label changes:

  • add P2: The PR is a normal-priority feature with a concrete merge-blocking UI/e2e regression and limited blast radius.
  • add proof: sufficient: Contributor real behavior proof is sufficient. Contributor screenshots directly show the new personal menu, org settings, organizations view, and publish switcher; I inspected the PNG artifacts, while the linked MOV could not be processed because ffprobe was unavailable.
  • add proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. Contributor screenshots directly show the new personal menu, org settings, organizations view, and publish switcher; I inspected the PNG artifacts, while the linked MOV could not be processed because ffprobe was unavailable.
  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (screenshot): Contributor screenshots directly show the new personal menu, org settings, organizations view, and publish switcher; I inspected the PNG artifacts, while the linked MOV could not be processed because ffprobe was unavailable.

Label justifications:

  • P2: The PR is a normal-priority feature with a concrete merge-blocking UI/e2e regression and limited blast radius.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (screenshot): Contributor screenshots directly show the new personal menu, org settings, organizations view, and publish switcher; I inspected the PNG artifacts, while the linked MOV could not be processed because ffprobe was unavailable.
  • proof: sufficient: Contributor real behavior proof is sufficient. Contributor screenshots directly show the new personal menu, org settings, organizations view, and publish switcher; I inspected the PNG artifacts, while the linked MOV could not be processed because ffprobe was unavailable.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. Contributor screenshots directly show the new personal menu, org settings, organizations view, and publish switcher; I inspected the PNG artifacts, while the linked MOV could not be processed because ffprobe was unavailable.
Evidence reviewed

Acceptance criteria:

  • [P1] bunx vitest run src/tests/header.test.tsx src/lib/activePublisher.test.tsx.
  • [P1] bun run test:pw:local-auth.
  • [P1] bun run ci:static.

What I checked:

  • Repository policy read: AGENTS.md was read fully; its ClawHub review guidance, Convex guidance requirement, specs intent, and real browser proof expectations were applied. (AGENTS.md:1, 6bbdec2a4ef8)
  • Convex guidance read: The Convex generated AI guidelines were read before reviewing the new Convex query surface. (convex/_generated/ai/guidelines.md:1, 6bbdec2a4ef8)
  • Header trigger source: The PR builds the account trigger title from publisher displayName before handle, which is the line tied to the failing local-auth assertions when displayName lacks an @handle. (src/components/Header.tsx:151, 5438e607ee46)
  • CI failure evidence: Ten local-auth shards fail at e2e/local-auth/helpers.ts:104 because header .user-trigger receives text like "Local Admin" or "Local Owner" instead of the expected @handle-bearing publisher identity. (e2e/local-auth/helpers.ts:104, ff5d12926fb0)
  • Visual proof inspected: Downloaded and inspected contributor screenshots for the personal menu, organization settings, organizations list, and publish switcher; the linked MOV preprocessing failed because ffprobe was unavailable.
  • Feature history: Current main history shows Header.tsx was recently worked on by Vyctor H. Brzezowski and originally includes the relevant signed-in trigger/helper behavior from Patrick Erichsen's broader UI cleanup. (src/components/Header.tsx:128, b1e077da382c)

Likely related people:

  • vyctorbrzezowski: Auth/header routing and header/search UX were recently changed on current main by this contributor, and the PR's central UI change is in the same header surface. (role: recent area contributor; confidence: high; commits: 1a3fdd8f5117, b1e077da382c; files: src/components/Header.tsx)
  • Patrick-Erichsen: Git blame points the existing header trigger logic, settings route structure, package manage context, and local-auth persona assertion helper to this current-main history. (role: introduced current signed-in flow and e2e helper behavior; confidence: high; commits: 1e7de4a2a263, 691ba20195cf; files: src/components/Header.tsx, src/routes/settings.tsx, e2e/local-auth/helpers.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal backlog priority with limited blast radius. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant