Skip to content

feat(plugins): unified Plugins surface (tenant-built + marketplace) + MIB service auth#76

Open
Nirpat3 wants to merge 53 commits into
mainfrom
feat/plugins-unified-surface
Open

feat(plugins): unified Plugins surface (tenant-built + marketplace) + MIB service auth#76
Nirpat3 wants to merge 53 commits into
mainfrom
feat/plugins-unified-surface

Conversation

@Nirpat3

@Nirpat3 Nirpat3 commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

What

Turns the AROS Plugins section into a unified view of two sources (grouped in one page):

  • Built for your business — apps this tenant generated via the App Factory (tenant_apps). Preview apps carry a Confirm & publish gate (preview → live, the human approval step).
  • Installed from marketplace — marketplace apps enabled with source = 'plugin' (what the page showed before).

Backend (src/server.ts)

  • GET /api/plugins — merges tenant_apps (built) + marketplace source=plugin (installed). Degrades to installed-only if tenant_apps isn't applied yet (staged-apply migration).
  • POST /api/plugins/:id/confirm — preview → live via appfactory.approveGoLive, service-role carrying the approver uuid; tenant-scoped before promote; owner/admin only.
  • Service auth (resolvePluginAccess) — MIB surfaces read a tenant's plugins service-to-service (AROS_SERVICE_TOKEN + allow-listed x-service-source + explicit ?tenantId), reusing the existing readServiceToken/tokensMatch primitives.

Frontend

  • api.ts: BuiltPlugin/InstalledPlugin types, listPlugins(), confirmPlugin().
  • InstalledResourcesPage: PluginsPage split into a grouped Built/Installed view with status pills + Confirm & publish; Connectors unchanged.
  • aros-shell.css: rsx-group__label + rsx-row__btn--primary.

Verification

  • apps/web tsc --noEmit clean.
  • Server tsc shows no new errors in changed files (the lone src/server.ts baseline error — discoveredStores ×3 — is pre-existing connector code).
  • Live HTTP smoke is gated on the tenant_apps migration (20260715) being applied + Supabase creds — an operator/deploy step, since the migration is staged-apply.

Companion

MIB read-side lands in Shreai/shre-command-center#<plugins-surface> (reads this GET /api/plugins contract).

🤖 Generated with Claude Code

rapidinfosoftllc and others added 30 commits July 15, 2026 19:55
Restyle /login and /signup to the AROS chat-first design (Claude Design
export): warm cream/gold palette (accent #B8842A), Newsreader serif display,
JetBrains Mono brand mark, "by ShreAI", light/dark toggle. Auth logic is
unchanged — Supabase email+password sign-in, the hosted-issuer OTP/workspace
branch, and the signup form/verify/done flow with intent picker + password
strength all preserved.

- aros-design.css: reconstructed light/dark design tokens + shared auth classes
- useArosTheme: persisted light/dark control via <html data-aros-theme>
- index.html: add Newsreader / JetBrains Mono / Hanken Grotesk fonts

Verified locally in both themes (typecheck clean). Not deployed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refine the token system + auth primitives (class names unchanged, so Login/
Signup inherit automatically): clean white cards on a soft warm radial ground,
hairline borders (--line-strong), layered soft card shadows, precise focus
rings (--focus-ring), restrained depth on the primary button (shadow + active
press), antialiased type, refined dark-mode surface elevation. Warm gold accent
(#B8842A light / #CD9F43 dark) preserved. Both light and dark verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the redesigned app shell behind a preview route (/preview/app, no auth):
left nav (Chat/Stores/Apps/Skills/Agents/Models/Permissions/Connection Health
+ Workspace section), connection-health widget, role switcher, user row, and a
themed topbar — all on the Phase-1 token system, light + dark.

- Concierge chat home (canned reply in preview; wires to router /v1/chat).
- Skills and Agents render real capability cards (wire to CapabilityCatalog
  kind="skills"/"agents"); other sections are placeholders mapping to existing
  surfaces (Stores/Apps→ConnectionsHub, Models→AIModels, Health→ConnectionHealth).
- Namespaced shell classes to rsx-* to avoid collision with aros.css
  (.aros-nav/.aros-card there leaked flex-direction:column into the nav).

Preview only — the live post-login app is unchanged. Web typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every nav section now renders real content in the chat-first design via a
data-driven panel system (stats / cards / rows / form / note), driven by
shellData.SECTIONS which mirrors the app's actual catalogs:

- Stores/Apps: real POS + app providers (RapidRMS connected w/ 5 stores,
  Verifone needs-attention, Clover/Square/NCR/Gilbarco; QuickBooks/Xero/Slack/…).
- Skills/Agents: the real CapabilityCatalog items (Daily Sales Summary, Inventory
  Reorder, …; Store Operations Agent, Inventory Agent, …) with tags + states.
- Models: the real AIModels providers (Shre local default, Anthropic, OpenAI,
  Gemini, Ollama) with model ids.
- Connection Health, Permissions, Team, Billing, Usage, Settings: real structure.

Static specs are cutover-ready — swap SECTIONS for live fetches and the render
layer is unchanged. Preview only, web typecheck clean, verified light + dark.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (Phase 4)

4-step modal wizard (provider → credentials → stores & access → review),
scoped to the two POS we support today: RapidRMS (HTTPS API key) and Verifone
Commander (secure tunnel: site controller IP + service creds + tunnel agent).
Faithful to the Claude Design export (provider blurbs, field labels, read/write
access copy). Opens from the Stores "Connect POS" CTA and the Concierge
"Connect Store" chip; finishes with a success toast. Light + dark verified.

Also narrowed the real ConnectionsHub POS list to RapidRMS + Verifone Commander
to match, and trimmed the Stores panel to the two providers.

Preview only (/preview/app). Web typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (Phase 5)

- Concierge chat now POSTs the real shre-router /v1/chat ({agentId,messages,
  stream}), renders the reply, typing indicator + graceful error bubble.
- Connect wizard now POSTs the real connectors API (POST /api/connectors:
  {type,name,config,secrets} then /api/connectors/test) with the auth session
  (Bearer + x-aros-tenant-id). Provider fields realigned to the backend
  contract: rapidrms-api (clientId/email/password), verifone-commander
  (commanderIp/username/password). Inline error surfaced on failure.
- Cutover behind a reversible flag: ?redesign=1 mounts AppShell as the real
  authed post-login app (?redesign=0 reverts); off by default, live app unchanged.

Validated in preview via network capture — both flows emit the correct request
shape; full round-trip needs a real session + running router/backend (staging).
Web typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…se 6)

Two-mode shell driven from a top bar (chat icon + menu):
- Chat mode: chat pane (left) + conversation canvas (right) — the canvas shows
  the conversation's outputs (KPI tiles, sales-by-store bar chart, low-stock
  table); new Canvas.tsx (example data; wired build receives mib-widget blocks).
- App mode: picking a section from the top menu closes chat and shows the nav
  sidebar (left) + section content (right).
- Chat pane gets a "New chat" affordance (resets the thread).
- Role switcher + all workspace menus (Team/Billing/Usage/Settings) moved out of
  the rail into a slide-in profile sidebar opened from the profile avatar.

goSection() routes 'chat' back to chat mode (no SectionPanel for chat). Verified:
menu→app→chat round-trips with zero page errors, light + dark.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…label, responsive (Phase 7)

Design consulted (subagent) then implemented:
- Home "Command Home" (Home.tsx): time-of-day greeting (Newsreader), Ask-Shre
  hero that opens chat, suggestion chips, 4 live KPI tiles, "Needs your approval"
  + "What Shre did" band, quick actions. Default view.
- Chat is now a toggle: clicking the chat icon SLIDES the chat rail in (animate
  grid-template-columns 440ms open / 320ms close, cubic-bezier(.16,1,.3,1)) with
  a fixed-width clipped rail (no text reflow) and a home<->canvas cross-fade;
  clicking again slides back to Home. prefers-reduced-motion fallback + focus mgmt.
- User info restored to BOTTOM-LEFT of the app-mode side menu (opens profile).
- Chat pane gains a model picker (Shre/Anthropic/OpenAI/Gemini) + New chat; Home's
  Ask-Shre and chips seed + focus the chat input.
- Whitelabel structure: branding.ts centralizes product/byline/mark/concierge
  (+ applyBrandTokens for runtime theme tokens); shell/chat consume it.
- Responsive: tablet (KPI/grid reflow) + phone (chat = full-screen slide-over,
  nav collapses to top menu, top-bar trims) breakpoints; verified 390px + desktop.

Web typecheck clean; verified home/chat/app + mobile, light + dark.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes nav discoverability: the top-bar menu (hamburger) icon now slides out the
complete sidebar as a left drawer from ANY mode (Home/Chat/section) — brand
header, full nav (Home, Chat, Stores, Apps, Skills, Agents, Models, Permissions,
Connection Health), and the user info row at the bottom (opens profile).
Replaces the small dropdown. App-mode docked sidebar unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r panes scroll

The home<->chat slide grid defined columns but no row track, so its implicit row
was content-sized instead of viewport-height. Inner panes then got height:100% of
content, pushing the chat composer (and lower section content) off-screen with no
scroll. Pin grid-template-rows: minmax(0,1fr) on .rsx2-slide + .rsx2-stage (and
min-height:0 on rail/stage) so the thread scrolls and the composer stays visible.
Verified: 6-message thread scrolls, composer visible at 593/760px.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ark-mode fix

- Right pane in chat is now two tabs: Canvas (conversation outputs) and History
  (recallable past conversations). Clicking a conversation loads it into the chat
  (ConciergeChat gains an `initial` prop); New chat clears it. CONVERSATIONS +
  HistoryPanel added.
- Layout consistency: Home and every section now share ONE docked left sidebar
  (nav + user info bottom-left). Chat is the only exception — its rail replaces
  the sidebar (slides in). The menu icon closes chat and returns to Home (desktop)
  / opens the nav drawer (mobile).
- Dark-mode fix: button wrappers (.rsx2-brand/.rsx2-ask/.rsx2-quick/.rsx2-userrow)
  now set color:var(--ink); their labels were falling back to the UA default
  (black) and were unreadable on dark surfaces.

Verified: sidebar hidden during chat, menu returns to home+sidebar, dark text
readable, no page errors. Web typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nt left panel

Data layer (redesign/data.ts) — THE GUARANTEE: demo persona/figures/catalogs
render ONLY when there is no real session (the public /preview/app). A logged-in
build shows real data + empty states, never demo content.
- useDemo() = !session. useIdentity() = real session name/tenant (live) or demo
  persona (preview). AppShell + UserRow now use identity, not the hardcoded USER.
- useSection(): stores/apps -> GET /api/connectors, models -> GET /api/settings/
  models (mapped to rows); other sections empty until wired. Demo -> static specs.
- useHomeData/useCanvasDemo/useConversations: demo content in preview, empty
  states live. SectionPanel/Home/Canvas/HistoryPanel render clean empty states.

Profile menu -> a LEFT panel (rsx2-profilenav) that overlaps the primary sidebar
with Home on top + Role + Workspace nav + Sign out, and PERSISTS while you click
through the account pages (was a right drawer that closed on each selection).

Verified in preview: demo data intact, profile is a left panel that survives
navigation, no page errors. Web typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live data (still gated behind a real session; preview stays demo):
- Skills/Agents -> GET /api/resources/skill|agent (tenant_resources) as cards.
- Models/Apps -> GET /api/resources/model|app as rows.
- Stores -> GET /api/connectors (RapidRMS/Verifone).
- Home -> GET /api/dashboard (activity + tasks->approvals) + /api/store/summary
  (KPIs). Mapping is defensive: a field that's absent yields an empty state,
  never a fabricated number.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ConciergeChat surfaces the raw reply via onReply; AppShell extracts structured
blocks (data.blocks / canvas.blocks / content_blocks) and feeds the Canvas, which
renders kpis/bars/table/text blocks defensively (unknown types skipped) — reset
on new/recalled chats. Demo canvas unchanged in preview; live shows blocks when a
reply carries them, else the empty state. Ready for the backend to emit
mib-widget content blocks on /v1/chat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s/permissions)

Live data for the last sections (demo path unchanged):
- Connection Health -> derived from /api/connectors (healthy/degraded/down + rows).
- Billing / Usage -> /api/billing/status?tenantId (plan/status; requests/spend).
- Team -> the logged-in user as a member row.
- Settings -> workspace-name prefilled from the tenant.
- Permissions -> role scopes shown always (product policy, not tenant data).
Absent fields still yield empty states, never fabricated values.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(converge)

Council-directed convergence after syncing main (which shipped the mib-widget
canvas in PR #38). Drop the redesign's divergent kpis/bars/text data.blocks
scheme; instead: ConciergeChat renders assistant messages via the shared
ChatMessageRenderer (markdown + inline mib-widgets) and derives canvas items from
the transcript via itemsFromMessages(); Canvas renders CanvasWidgetItems with the
shared WidgetRenderer. warmPalette() feeds the renderer the live design tokens.

Web typecheck clean; preview verified (ChatMessageRenderer active, demo canvas
intact, no page errors).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	apps/web/src/app/App.tsx
#	apps/web/src/redesign/AppShell.tsx
…ign (soft launch)

The changelog had lapsed after 0.1.0 while tags advanced to v0.4.0. Backfilled
0.2.0/0.3.0/0.3.1/0.4.0 from their tags, added [0.5.0] for the flag-gated
redesign soft-launch (live 2026-07-16, default unchanged), and expanded
[Unreleased] with the flip-to-default + convergence follow-ups. Bumped root
version 0.3.1 -> 0.5.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ect 0.5.0 note)

Concurrent workstream inverted the gating on the live fork — the redesign now
renders by default for authenticated users; ?redesign=0 sets aros-shell-legacy
as the opt-out. Updated the [0.5.0] note + [Unreleased] (pre-flip checks are now
post-flip verification).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
rapidinfosoftllc and others added 20 commits July 16, 2026 00:04
Add `Cache-Control: private, no-store` to the app-shell HTML (and the
index.html SPA fallback) so no shared cache/CDN stores the authenticated
bootstrap. Fingerprinted /assets/* stay `public, immutable`.

Closes the post-flip cache-control hardening item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Record the app-shell no-store fix and the verified tenant-isolation /
demo-leak audit results. Clears the post-flip verification item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ace plugins

Repoint the AROS Plugins section from marketplace-only to a UNIFIED view of
two sources (DECIDED: unified), grouped in one page:

  * Built for your business — apps this tenant generated via the App Factory
    (public.tenant_apps). Preview apps carry a "Confirm & publish" gate that
    promotes preview -> live (the human approval step). This is the
    "StorePulse-like internal app I built" case.
  * Installed from marketplace — marketplace apps enabled with source=plugin
    (the rows the page showed before).

Backend (src/server.ts):
  * GET  /api/plugins            — merges tenant_apps ('built') + marketplace
                                    source=plugin ('installed'); degrades to
                                    installed-only if tenant_apps isn't applied
                                    yet (staged-apply migration).
  * POST /api/plugins/:id/confirm — preview -> live via appfactory.approveGoLive,
                                    service-role carrying the approver uuid;
                                    tenant-scoped before promote; owner/admin only.

Frontend:
  * api.ts: BuiltPlugin/InstalledPlugin types, listPlugins(), confirmPlugin().
  * InstalledResourcesPage: PluginsPage split out into a grouped Built/Installed
    view with status pills and a Confirm & publish action; Connectors unchanged.
  * aros-shell.css: rsx-group__label + rsx-row__btn--primary.

Contract is shared: MIB surfaces read the same GET /api/plugins.

Verified: apps/web tsc --noEmit clean; server tsc shows no new errors in the
changed files (the lone src/server.ts baseline error, discoveredStores x3, is
pre-existing connector code). Live HTTP smoke is gated on the tenant_apps
migration (20260715) being applied + Supabase creds — an operator/deploy step.
MIB surfaces (mib007 / shre-command-center, mib-desktop) can't present an
end-user's AROS session — they read a tenant's plugins service-to-service.
Add a shared resolvePluginAccess() that authorizes a plugins request as EITHER
a workspace user (cookie/bearer -> tenant membership) OR a trusted service
(AROS_SERVICE_TOKEN + allow-listed x-service-source + explicit ?tenantId),
reusing the existing readServiceToken()/tokensMatch() primitives.

  * GET  /api/plugins            — service path lists any tenant's plugins by
                                    ?tenantId (same as the store-summary path).
  * POST /api/plugins/:id/confirm — service path is pre-authorized (the MIB
                                    surface already gated the human) and MUST
                                    carry the approving user's uuid in body.userId,
                                    which preview->live records as the approver.

Allow-list: shre-router, mib007, shre-command-center, mib-desktop. Token is the
real gate; the source header is allow-listed for audit clarity.

Verified: apps/web tsc clean; server tsc shows no new errors in changed files.
GET /api/plugins/all — SERVICE AUTH ONLY (no end-user path); returns every
tenant's plugins grouped by tenant, with tenant display names. Powers the
mib-desktop ops console's cross-tenant Plugins view. Capped (default 500,
max 2000) and degrades gracefully if tenant_apps isn't applied yet.

Verified: server tsc shows no new errors in the changed file.
@Nirpat3
Nirpat3 force-pushed the feat/plugins-unified-surface branch from 175c8f1 to b35ac19 Compare July 17, 2026 04:12
…uilt app

POST /api/plugins (SERVICE AUTH ONLY) closes the App Factory loop: it creates a
tenant_apps row (status 'draft') + the app's private schema + scoped role via
appfactory.provisionApp, so a built app appears under Plugins and can walk the
draft -> preview -> live lifecycle.

  * appfactory/sql-executor.ts — config-gated privileged Postgres executor
    (APP_FACTORY_DATABASE_URL or ~/.shre/vault/app-factory-db.json) using the
    same pg-Pool + file-vault conventions as the rapidrms analytics connector.
    Null when unconfigured -> the endpoint 501s (same operator-wiring posture as
    the staged-apply tenant_apps migration). Also vaults the generated role
    password to ~/.shre/vault/app-<subdomain>.json (0600) — never over the wire.
  * src/server.ts — handlePluginProvision: validates tenantId/slug/displayName,
    provisions, vaults the credential, returns the created app (no secret).

This is the AROS half of the loop; the Sia App Factory write-back (call this on
build/plan approval) is the remaining connective step.

Verified: server+appfactory tsc show no new errors in changed files.
The previous commit shipped appfactory/sql-executor.ts but its server.ts
wiring (import, handlePluginProvision, POST /api/plugins route) was dropped.
Restore it so the provision write-path is actually reachable.

Verified: server tsc shows no new errors in changed files.
Covers the staged tenant_apps migration, the provisioning DB config
(APP_FACTORY_DATABASE_URL / vault), the MIB service token + allow-list, curl
verification for every endpoint, the lifecycle guardrails, and rollback. Each
item degrades safely until configured.
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.

2 participants