Marketplace Redesign Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
Goal: Joblogic-mirrored marketplace UX on market.odontox.io plus a click-to-open Marketplace flyout in go.odontox.io’s tenant sidebar (admin/owner only), with image upload + module-tagging + dependency tagging in the superadmin editor.
Architecture: One additive Postgres migration adds related_modules and dependencies to marketplace_listings. Server route includes them in responses; new endpoint accepts image uploads to R2. Frontend changes split across two apps: marketplace-app/ gets a hero + carousel + faceted sidebar + sectioned/flat grid + redesigned detail page + new My Apps page + global footer + indigo tokens. ui/ (go.odontox.io) gains a Marketplace nav item + flyout panel.
Tech Stack: React + Vite + Tailwind + TanStack Query, Hono on Cloudflare Workers, Drizzle + Postgres (Neon), Cloudflare R2 for image storage, lucide-react icons.
Spec: docs/superpowers/specs/2026-05-21-marketplace-redesign-design.md
File Map
Server:- Create:
server/drizzle/0050_marketplace_related_modules_deps.sql - Modify:
server/src/schema/marketplace_listings.ts(add two columns) - Modify:
server/src/routes/marketplace.ts(expose new fields inpublicListingShape) - Modify:
server/src/routes/superadmin/marketplace.ts(accept new fields on update, add upload endpoint) - Modify:
docs/api-reference.md(document upload endpoint)
ui/ (go.odontox.io):
- Modify:
ui/src/hooks/useNavItems.ts(add Marketplace item for admin/owner) - Create:
ui/src/components/MarketplaceFlyout.tsx - Modify:
ui/src/components/appSidebar.tsx(wire flyout to nav item) - Modify:
ui/src/pages/sign-in.tsx(bumpAPP_VERSION)
marketplace-app/:
- Modify:
marketplace-app/src/index.css(replace teal tokens with indigo, mirrorui/src/index.css) - Modify:
marketplace-app/src/components/MarketplaceShell.tsx(Home/My Apps nav links, footer) - Create:
marketplace-app/src/components/MarketplaceFooter.tsx - Create:
marketplace-app/src/components/marketplace/MarketplaceHero.tsx - Create:
marketplace-app/src/components/marketplace/RecommendedForYouRow.tsx - Create:
marketplace-app/src/components/marketplace/MarketplaceSidebar.tsx - Create:
marketplace-app/src/components/marketplace/SectionedListings.tsx - Modify:
marketplace-app/src/pages/MarketplaceIndex.tsx(compose new sections) - Modify:
marketplace-app/src/pages/MarketplaceDetail.tsx(breadcrumbs + right rail + restructured) - Create:
marketplace-app/src/components/marketplace/DetailRail.tsx - Create:
marketplace-app/src/pages/MyApps.tsx - Modify:
marketplace-app/src/App.tsx(route additions, redirect/requests) - Modify:
marketplace-app/src/hooks/use-marketplace-listings.ts(add fields toMarketplaceListingtype) - Modify:
marketplace-app/public/hero-mockup.webp(new asset — placeholder OK for v1)
- Modify:
ui/src/components/superadmin/MarketplaceListingEditor.tsx(image upload, related modules multi-select, dependencies chip input)
Task 1: Migration — add related_modules and dependencies columns
Files:
-
Create:
server/drizzle/0050_marketplace_related_modules_deps.sql -
Modify:
server/src/schema/marketplace_listings.ts - Step 1: Write the migration SQL
server/drizzle/0050_marketplace_related_modules_deps.sql:
- Step 2: Update Drizzle schema to match
server/src/schema/marketplace_listings.ts, after the securityBadges line and before status, add:
import { sql } from 'drizzle-orm'; if not already imported.
- Step 3: Verify TypeScript compiles
pnpm --filter server typecheck
Expected: PASS
- Step 4: Apply migration to local dev DB only
pnpm --filter server drizzle:migrate
Expected: migration 0050 applied. Do NOT run against prod.
- Step 5: Commit
Task 2: Expose related_modules and dependencies in public listing responses
Files:
-
Modify:
server/src/routes/marketplace.ts -
Modify:
marketplace-app/src/hooks/use-marketplace-listings.ts -
Step 1: Verify
publicListingShapereturns the new fields
server/src/routes/marketplace.ts. The current shape spreads all columns except updatedBy and status. Since related_modules and dependencies are now part of the row, they’ll flow through automatically. No code change needed — confirm by reading lines 28-34.
- Step 2: Add the new fields to the client TypeScript type
marketplace-app/src/hooks/use-marketplace-listings.ts, add two fields to the MarketplaceListing interface (before subscriptionState):
MarketplaceListingDetail['listing']’s type if it’s a separate declaration — it uses Omit<MarketplaceListing, 'subscriptionState'>, so it picks them up automatically.
- Step 3: Write a sanity test for the response shape
server/src/routes/__tests__/marketplace-listings-shape.test.ts:
- Step 4: Run the test
pnpm --filter server test marketplace-listings-shape
Expected: PASS
- Step 5: Commit
Task 3: Image-upload endpoint for superadmin marketplace
Files:-
Modify:
server/src/routes/superadmin/marketplace.ts -
Modify:
docs/api-reference.md - Step 1: Inspect the existing R2 service
server/src/lib/r2.ts lines 68-120 to confirm R2Service.uploadFile(key, bytes, mime, opts) signature, and server/src/routes/billing.ts lines 2110-2160 to confirm the formData → magic-bytes → uploadFile pattern.
- Step 2: Write a failing test for the upload endpoint
server/src/routes/__tests__/superadmin-marketplace-upload.test.ts:
test-app.ts helpers don’t match this exactly, adjust to match the existing test patterns under server/src/routes/__tests__/. Read server/src/routes/__tests__/superadmin-marketplace.test.ts for the canonical pattern.)
- Step 3: Run and confirm failure
pnpm --filter server test superadmin-marketplace-upload
Expected: FAIL — route does not exist (404).
- Step 4: Implement the endpoint
server/src/routes/superadmin/marketplace.ts, add at the bottom of the route file (before the export):
detectImageMime doesn’t exist, add a small inline helper above the route that checks the first 4 bytes:
-
89 50 4e 47→ png -
ff d8 ff→ jpeg -
52 49 46 46+ bytes 8-11 =57 45 42 50→ webp - Step 5: Run tests
pnpm --filter server test superadmin-marketplace-upload
Expected: PASS for all three cases.
- Step 6: Document the endpoint in
docs/api-reference.md
docs/api-reference.md and add:
- Step 7: Commit
Task 4: Superadmin editor — image upload, related modules, dependencies
Files:-
Modify:
ui/src/components/superadmin/MarketplaceListingEditor.tsx - Step 1: Read the existing editor (lines 130-300, 400-470) to understand the current screenshot input, form shape, and save mutation.
-
Step 2: Add
relatedModulesanddependenciesto the form type
Form type near the top of the file (around line 50), add:
[] when undefined) and the save payload sent to the server.
- Step 3: Verify the server PATCH/PUT accepts these fields
server/src/routes/superadmin/marketplace.ts for the listing-update handler. If it whitelists fields, add relatedModules and dependencies. If it spreads body into the DB call, no change needed. If you added fields, also write a quick test under __tests__/.
- Step 4: Replace screenshot R2-key input with file upload
screenshotAlt text input as a sibling so users still set alt text before picking the file.
- Step 5: Do the same for icon when
iconKind === 'upload'
iconKind switches between lucide and upload). When upload is selected, render the same file input pattern. On success, set form.iconValue to the returned key.
- Step 6: Add a “Recommended modules” multi-select
ui/src/components/ui/):
ui/src/lib/modules.ts that re-exports the same constant from shared/ or duplicates it from server/src/constants/modules.ts. Prefer a shared package if the workspace already has one.
- Step 7: Add a “Dependencies” chip input
- Step 8: Manual smoke
pnpm --filter ui dev. Open the superadmin marketplace listing editor. Confirm:
- Selecting a file uploads and a thumbnail appears with the returned key.
- Toggling a recommended module checkbox persists on save.
- Adding/removing a dependency chip persists on save.
- Step 9: Commit
Task 5: Switch marketplace-app to indigo CSS tokens
Files:
-
Modify:
marketplace-app/src/index.css -
Step 1: Diff against
ui/src/index.css
:root and .dark block from ui/src/index.css (the section defining --background, --foreground, --primary, etc.) and replace the corresponding block in marketplace-app/src/index.css.
- Step 2: Grep for hardcoded teal in marketplace-app
grep -rn "teal-\|teal\b\|#0d9488\|#14b8a6\|cyan-" marketplace-app/src/
Expected: list of leaks.
- Step 3: Replace each leak with semantic tokens
text-teal-* → text-primary, bg-teal-* → bg-primary, border-teal-* → border-primary (or border-accent if it was a soft accent). Keep ListingCard hero color logic that uses the heroColor data field — that’s a per-listing token override, leave it.
- Step 4: Smoke check both themes
pnpm --filter marketplace-app dev. Visit http://localhost:5174/. Toggle theme. Confirm primary buttons and active nav states are indigo, no teal remains.
- Step 5: Commit
Task 6: Global footer
Files:-
Create:
marketplace-app/src/components/MarketplaceFooter.tsx -
Modify:
marketplace-app/src/components/MarketplaceShell.tsx - Step 1: Write the footer
marketplace-app/src/components/MarketplaceFooter.tsx:
- Step 2: Mount in shell
marketplace-app/src/components/MarketplaceShell.tsx, just after <main>...</main>:
- Step 3: Manual smoke
/, /apps/ai-rephraser, and a 404 — confirm footer renders on each.
- Step 4: Commit
Task 7: Shell nav links — Home and My Apps
Files:-
Modify:
marketplace-app/src/components/MarketplaceShell.tsx - Step 1: Add NavLinks to the header
MarketplaceShell.tsx, after the logo lockup and before the right-side controls, add:
- Step 2: Commit
Task 8: Hero banner
Files:-
Create:
marketplace-app/src/components/marketplace/MarketplaceHero.tsx -
Add asset:
marketplace-app/public/hero-mockup.webp(use any OdontoX module screenshot — appointments calendar is a good default; placeholder is fine for v1) - Step 1: Implement the hero component
- Step 2: Add a placeholder image
marketplace-app/public/hero-mockup.webp. The page must not crash if it 404s — <img> will render the broken icon, which is acceptable for v1 but should be replaced before deploy.
- Step 3: Commit
Task 9: “Recommended for you” carousel
Files:-
Create:
marketplace-app/src/components/marketplace/RecommendedForYouRow.tsx -
Create:
marketplace-app/src/hooks/use-clinic-modules.ts - Step 1: Hook to read enabled modules for the active clinic
marketplace-app already exposes the clinic’s enabled modules. If not, add use-clinic-modules.ts:
ui/, mirror that.)
- Step 2: Implement the carousel
- Step 3: Commit
Task 10: Faceted sidebar (Recommended for + Categories)
Files:-
Create:
marketplace-app/src/components/marketplace/MarketplaceSidebar.tsx - Step 1: Implement sidebar
- Step 2: Create the shared modules lib for marketplace-app
marketplace-app/src/lib/modules.ts doesn’t exist, create it duplicating just the key and label fields from server/src/constants/modules.ts (a thin client-side mirror is acceptable since the list is small and rarely changes).
- Step 3: Commit
Task 11: Sectioned grid + flat-when-filtered main column
Files:-
Create:
marketplace-app/src/components/marketplace/SectionedListings.tsx - Step 1: Implement
- Step 2: Commit
Task 12: Compose the new Home page
Files:-
Modify:
marketplace-app/src/pages/MarketplaceIndex.tsx - Step 1: Rewrite the page
MarketplaceIndex.tsx with:
- Step 2: Manual smoke
/. Verify:
- Hero renders.
- Recommended-for-you row renders (or is hidden if none match).
- Sidebar with Recommended for + Categories renders on lg+.
- Default view shows sectioned rows by category.
- Clicking a sidebar item collapses to a flat grid.
- “Clear filter” returns to sectioned rows.
- Step 3: Commit
Task 13: Restructure App Detail page with breadcrumbs and right rail
Files:-
Create:
marketplace-app/src/components/marketplace/DetailRail.tsx -
Modify:
marketplace-app/src/pages/MarketplaceDetail.tsx - Step 1: Implement DetailRail
- Step 2: Rewrite MarketplaceDetail layout
marketplace-app/src/pages/MarketplaceDetail.tsx. Wrap the existing content in the new structure:
- Step 3: Manual smoke
/apps/ai-rephraser. Verify:
- Breadcrumbs render and link back to Home.
- Hero + Open/Subscribe CTA still works.
- Right rail shows Version / Last updated / Categories / Recommended for / Dependencies.
-
“See recent releases →” link points to
/apps/ai-rephraser/releases. - On narrow widths the rail stacks below content.
- Step 4: Commit
Task 14: Releases sub-route (modal or full page)
Files:-
Create:
marketplace-app/src/pages/MarketplaceReleases.tsx -
Modify:
marketplace-app/src/App.tsx(add route) - Step 1: Implement page
- Step 2: Wire route
marketplace-app/src/App.tsx, add a route under the MarketplaceShell layout:
- Step 3: Commit
Task 15: My Apps page (Subscribed + Requests tabs)
Files:-
Create:
marketplace-app/src/pages/MyApps.tsx -
Modify:
marketplace-app/src/App.tsx(add route, redirect/requests) - Step 1: Implement
- Step 2: Routes
marketplace-app/src/App.tsx:
-
Add
<Route path="my-apps" element={<MyApps />} />under theMarketplaceShelllayout. -
Replace any existing top-level
<Route path="requests" ...>with<Route path="requests" element={<Navigate to="/my-apps?tab=requests" replace />} />(importNavigatefrom react-router-dom). - Step 3: Manual smoke
-
Visit
/my-apps→ defaults to Subscribed tab. -
Visit
/my-apps?tab=requests→ opens Requests tab. -
Visit
/requests→ redirects to/my-apps?tab=requests. - Click between tabs and refresh — URL stays in sync.
- Step 4: Commit
Task 16: go.odontox.io — Marketplace nav item (admin/owner only)
Files:
-
Modify:
ui/src/hooks/useNavItems.ts - Step 1: Find the tenant nav blocks
ui/src/hooks/useNavItems.ts around lines 1-200. Identify the 'admin' (and 'owner' if separate) role blocks.
- Step 2: Add the Marketplace item
doctor, receptionist, nurse, lab_tech blocks.
- Step 3: Confirm
LayoutGridis imported
LayoutGrid to the imports.
- Step 4: Commit
Task 17: MarketplaceFlyout component
Files:
-
Create:
ui/src/components/MarketplaceFlyout.tsx - Step 1: Implement
ui/ already — e.g., a lucide-name-to-component helper — substitute it for the placeholder.)
- Step 2: Commit
Task 18: Wire flyout into sidebar
Files:-
Modify:
ui/src/components/appSidebar.tsx -
Step 1: Read the existing nav-click handler in
appSidebar.tsx
setActivePage or similar). We need to intercept the 'marketplace' id to open the flyout instead of navigating.
- Step 2: Add flyout state and intercept
item is rendered. Wrap (or modify) the click handler so when item.id === 'marketplace':
- Step 3: Close flyout on route change
- Step 4: Manual smoke
pnpm --filter ui dev. Sign in as an admin/owner against the local dev server. Confirm:
- “Marketplace” nav item appears.
- Clicking it opens the flyout to the right of the sidebar.
- Esc / outside click / clicking another nav item closes it.
- “Explore more ↗” opens
https://market.odontox.ioin a new tab.
- Step 5: Commit
Task 19: Bump APP_VERSION
Files:-
Modify:
ui/src/pages/sign-in.tsx -
Step 1: Find and bump
APP_VERSION
grep -n "APP_VERSION" ui/src/pages/sign-in.tsx
Increment the patch component (or minor if appropriate to the release plan).
- Step 2: Commit
Task 20: End-to-end smoke + Playwright check
Files:-
Create:
ui/e2e/marketplace-flyout.spec.ts - Step 1: Write the spec
signIn helper invocation to match the existing pattern in ui/e2e/.
- Step 2: Run
pnpm --filter ui exec playwright test marketplace-flyout
Expected: PASS.
- Step 3: Commit
Self-Review
Spec coverage check (cross-walked spec sections to tasks):- Sidebar flyout & nav item → Tasks 16, 17, 18, 20
- Marketplace shell (theming, nav links, footer) → Tasks 5, 6, 7
- Home page (hero, carousel, sidebar, sectioned/flat grid) → Tasks 8, 9, 10, 11, 12
- App detail page (breadcrumbs, right rail, releases sub-route) → Tasks 13, 14
- My Apps page → Task 15
- Data model additions → Task 1
- Server response surfaces new fields → Task 2
- Superadmin editor (image upload, modules, dependencies) → Tasks 3, 4
- API docs update → Task 3
- APP_VERSION bump → Task 19
SidebarFilter defined in Task 10, used in Tasks 11 and 12 with matching shape. MarketplaceListing.relatedModules / dependencies added in Task 2 are read in Tasks 9, 10, 11, 12, 13 — same names everywhere.
Risks worth flagging to whoever executes:
- Task 9 assumes
/clinics/:id/modulesexists. If the endpoint shape differs inmarketplace-app, mirror whatever the existing app uses to fetch enabled modules. - Task 16 assumes admin/owner are distinct roles in
useNavItems.ts. If your codebase uses a single'admin'role for both, add the item only to that block. - Task 18’s sidebar wiring depends on the specific click-handler shape in
appSidebar.tsx— read the existing pattern before editing.
Execution Handoff
Plan complete and saved todocs/superpowers/plans/2026-05-21-marketplace-redesign.md. Two execution options:
1. Subagent-Driven (recommended) — fresh subagent per task, review between tasks, fast iteration.
2. Inline Execution — execute tasks in this session using executing-plans, batch with checkpoints for review.
Which approach?
