Plans 03 and 04 both modify setups/$setupId.tsx. Per wave assignment rules, file overlap requires sequential execution. Plan 04 now depends on Plan 03 and runs in Wave 4. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.6 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 32-setup-sharing-system | 04 | execute | 4 |
|
|
true |
|
|
Purpose: This completes the user-facing share flow (D-06, D-17). When someone receives a share link, they can view the setup without authentication.
Output: Updated setup detail page with share token detection and shared viewing mode.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/32-setup-sharing-system/32-CONTEXT.md @.planning/phases/32-setup-sharing-system/32-UI-SPEC.md @.planning/phases/32-setup-sharing-system/32-01-SUMMARY.md @.planning/phases/32-setup-sharing-system/32-02-SUMMARY.mdShared access API endpoint (from Plan 02):
GET /api/shared/:token → Setup object with items array (same format as public view)
Returns 404 for invalid/expired/revoked tokens
Short URL redirect (from Plan 02):
GET /s/:token → 302 redirect to /setups/:setupId?share=:token
From src/client/routes/setups/$setupId.tsx (current structure):
// Three-way data source: private (auth), public (no auth), shared (token)
const { data: auth } = useAuth();
const isAuthenticated = !!auth?.user;
const privateSetup = useSetup(isAuthenticated ? numericId : null);
const publicSetup = usePublicSetup(!isAuthenticated ? numericId : null);
From @tanstack/react-router:
// URL search params access
const search = Route.useSearch(); // needs searchSchema defined on route
export function useSharedSetup(token: string | null) {
return useQuery({
queryKey: ["shared-setup", token],
queryFn: () => apiGet<SetupWithItems>(`/api/shared/${token}`),
enabled: !!token,
retry: false, // Don't retry on 404
});
}
Use the same SetupWithItems type used by useSetup and usePublicSetup.
Update src/client/routes/setups/$setupId.tsx:
- Add search params validation to the route definition to capture the
sharequery param:
import { z } from "zod";
export const Route = createFileRoute("/setups/$setupId")({
component: SetupDetailPage,
validateSearch: z.object({
share: z.string().optional(),
}),
});
- In
SetupDetailPage, detect the share token:
const { share: shareToken } = Route.useSearch();
- Update the three-way data source logic:
const { data: auth } = useAuth();
const isAuthenticated = !!auth?.user;
// Priority: share token > authenticated owner > public viewer
const sharedSetup = useSharedSetup(shareToken ?? null);
const privateSetup = useSetup(!shareToken && isAuthenticated ? numericId : null);
const publicSetup = usePublicSetup(!shareToken && !isAuthenticated ? numericId : null);
const isSharedView = !!shareToken;
const { data: setup, isLoading, isError } = isSharedView
? sharedSetup
: isAuthenticated
? privateSetup
: publicSetup;
- Add shared banner (per 32-UI-SPEC.md) — render above the header bar when
isSharedView:
{isSharedView && setup && (
<div className="flex items-center gap-2 px-4 py-2 bg-blue-50 border-b border-blue-100">
<LucideIcon name="link" size={16} className="text-blue-500" />
<span className="text-sm text-blue-700">Shared setup</span>
</div>
)}
- Add error state for invalid/expired share tokens:
{isSharedView && isError && (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 text-center">
<LucideIcon name="link" size={48} className="text-gray-300 mx-auto mb-4" />
<h2 className="text-xl font-semibold text-gray-900 mb-2">Link not available</h2>
<p className="text-sm text-gray-500">This share link has expired or is no longer valid.</p>
</div>
)}
-
Hide owner-only controls when in shared view — conditionally hide these elements when
isSharedViewis true:- Add Items button (both desktop and mobile variants)
- Share button (both desktop and mobile variants)
- Delete Setup button (both desktop and mobile variants)
- Classification dropdowns on items
- Remove item buttons
Wrap each with:
{!isSharedView && isAuthenticated && ( ... )} -
The shared view shows the same read-only content as the public view: item list grouped by category, weight summary card, setup name header. grep -q "useSharedSetup" src/client/hooks/useSetups.ts && grep -q "shareToken|share:" src/client/routes/setups/$setupId.tsx && grep -q "Shared setup" src/client/routes/setups/$setupId.tsx && bun run lint && echo "PASS" || echo "FAIL" <acceptance_criteria>
src/client/hooks/useSetups.tsexportsuseSharedSetup(token)that fetches/api/shared/:tokensrc/client/routes/setups/$setupId.tsxvalidatessharesearch param via Zod- When
?share=tokenis present, setup data is fetched via shared endpoint (not owner or public) - Shared banner (
Shared setupwith link icon in blue-50) appears at top of page when share token present - Invalid/expired token shows error state with "Link not available" message
- Owner-only controls (add items, share, delete, classification, remove item) are hidden in shared view
bun run lintpasses </acceptance_criteria> Shared setup viewer with token detection, shared banner, error handling, and read-only mode
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| URL search params | Share token from URL — untrusted user input |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-32-09 | Spoofing | share token in URL | mitigate | Token validated server-side by /api/shared/:token — client only passes through, no client-side authorization decisions |
| T-32-10 | Information Disclosure | shared view content | accept | Shared setup data is intentionally visible to anyone with the token — this is the feature |
| </threat_model> |
<success_criteria>
- Share links use
/s/{token}short URL AND/setups/:id?share={token}(per D-06) - Shared setup viewer works for anonymous users (per D-17)
- No owner-only actions visible in shared view
- No changes to discovery feed or profile page (per D-18) </success_criteria>