@autoview/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +407 -0
- package/lib/AutoViewAgent.d.ts +109 -0
- package/lib/AutoViewAgent.js +123 -0
- package/lib/AutoViewAgent.js.map +1 -0
- package/lib/agent/emitMcpServer.d.ts +15 -0
- package/lib/agent/emitMcpServer.js +157 -0
- package/lib/agent/emitMcpServer.js.map +1 -0
- package/lib/agent/emitReport.d.ts +14 -0
- package/lib/agent/emitReport.js +85 -0
- package/lib/agent/emitReport.js.map +1 -0
- package/lib/agent/toolSurface.d.ts +130 -0
- package/lib/agent/toolSurface.js +342 -0
- package/lib/agent/toolSurface.js.map +1 -0
- package/lib/agent/verifyAgentTasks.d.ts +87 -0
- package/lib/agent/verifyAgentTasks.js +126 -0
- package/lib/agent/verifyAgentTasks.js.map +1 -0
- package/lib/cli/main.d.ts +2 -0
- package/lib/cli/main.js +295 -0
- package/lib/cli/main.js.map +1 -0
- package/lib/compiler/AutoViewInterfaceCompiler.d.ts +27 -0
- package/lib/compiler/AutoViewInterfaceCompiler.js +68 -0
- package/lib/compiler/AutoViewInterfaceCompiler.js.map +1 -0
- package/lib/constants/AutoViewFrontendTemplate.d.ts +1 -0
- package/lib/constants/AutoViewFrontendTemplate.js +46 -0
- package/lib/constants/AutoViewFrontendTemplate.js.map +1 -0
- package/lib/constants/AutoViewSystemPromptConstant.d.ts +5 -0
- package/lib/constants/AutoViewSystemPromptConstant.js +4 -0
- package/lib/constants/AutoViewSystemPromptConstant.js.map +1 -0
- package/lib/context/IAutoViewAgentContext.d.ts +60 -0
- package/lib/context/IAutoViewAgentContext.js +3 -0
- package/lib/context/IAutoViewAgentContext.js.map +1 -0
- package/lib/fromSwagger.d.ts +53 -0
- package/lib/fromSwagger.js +513 -0
- package/lib/fromSwagger.js.map +1 -0
- package/lib/generateDeterministic.d.ts +26 -0
- package/lib/generateDeterministic.js +75 -0
- package/lib/generateDeterministic.js.map +1 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +41 -0
- package/lib/index.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoView.d.ts +17 -0
- package/lib/orchestrate/orchestrateAutoView.js +491 -0
- package/lib/orchestrate/orchestrateAutoView.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.d.ts +37 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.js +109 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewRender.d.ts +133 -0
- package/lib/orchestrate/orchestrateAutoViewRender.js +943 -0
- package/lib/orchestrate/orchestrateAutoViewRender.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.d.ts +24 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js +92 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewReview.d.ts +48 -0
- package/lib/orchestrate/orchestrateAutoViewReview.js +328 -0
- package/lib/orchestrate/orchestrateAutoViewReview.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.d.ts +45 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.js +586 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.d.ts +26 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.js +85 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.d.ts +96 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.js +3 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.d.ts +38 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.d.ts +38 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.d.ts +40 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.d.ts +63 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.js +3 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.d.ts +37 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js.map +1 -0
- package/lib/orchestrate/utils/HistoryMessage.d.ts +10 -0
- package/lib/orchestrate/utils/HistoryMessage.js +25 -0
- package/lib/orchestrate/utils/HistoryMessage.js.map +1 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.d.ts +53 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.js +362 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.js.map +1 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.d.ts +4 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.js +233 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.js.map +1 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.d.ts +22 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.js +154 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.js.map +1 -0
- package/lib/orchestrate/utils/cacheNodeModules.d.ts +31 -0
- package/lib/orchestrate/utils/cacheNodeModules.js +134 -0
- package/lib/orchestrate/utils/cacheNodeModules.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.d.ts +37 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.js +192 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.d.ts +22 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js +29 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.d.ts +19 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.js +30 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.js.map +1 -0
- package/lib/orchestrate/utils/executeCachedBatch.d.ts +22 -0
- package/lib/orchestrate/utils/executeCachedBatch.js +64 -0
- package/lib/orchestrate/utils/executeCachedBatch.js.map +1 -0
- package/lib/orchestrate/utils/loadShoppingFixture.d.ts +33 -0
- package/lib/orchestrate/utils/loadShoppingFixture.js +17 -0
- package/lib/orchestrate/utils/loadShoppingFixture.js.map +1 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.d.ts +24 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.js +77 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.js.map +1 -0
- package/lib/orchestrate/utils/renderJsonSchema.d.ts +23 -0
- package/lib/orchestrate/utils/renderJsonSchema.js +122 -0
- package/lib/orchestrate/utils/renderJsonSchema.js.map +1 -0
- package/lib/orchestrate/utils/renderResourcePage.d.ts +36 -0
- package/lib/orchestrate/utils/renderResourcePage.js +1415 -0
- package/lib/orchestrate/utils/renderResourcePage.js.map +1 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.d.ts +109 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.js +274 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.js.map +1 -0
- package/lib/preview/renderPreview.d.ts +22 -0
- package/lib/preview/renderPreview.js +198 -0
- package/lib/preview/renderPreview.js.map +1 -0
- package/lib/typings/compiler.d.ts +39 -0
- package/lib/typings/compiler.js +3 -0
- package/lib/typings/compiler.js.map +1 -0
- package/lib/typings/events.d.ts +106 -0
- package/lib/typings/events.js +3 -0
- package/lib/typings/events.js.map +1 -0
- package/lib/typings/index.d.ts +10 -0
- package/lib/typings/index.js +27 -0
- package/lib/typings/index.js.map +1 -0
- package/lib/typings/misc.d.ts +78 -0
- package/lib/typings/misc.js +3 -0
- package/lib/typings/misc.js.map +1 -0
- package/lib/utils/ArrayUtil.d.ts +8 -0
- package/lib/utils/ArrayUtil.js +30 -0
- package/lib/utils/ArrayUtil.js.map +1 -0
- package/lib/utils/StringUtil.d.ts +11 -0
- package/lib/utils/StringUtil.js +28 -0
- package/lib/utils/StringUtil.js.map +1 -0
- package/lib/utils/classifyEndpoints.d.ts +62 -0
- package/lib/utils/classifyEndpoints.js +216 -0
- package/lib/utils/classifyEndpoints.js.map +1 -0
- package/lib/utils/endpointFilter.d.ts +26 -0
- package/lib/utils/endpointFilter.js +0 -0
- package/lib/utils/endpointFilter.js.map +1 -0
- package/lib/utils/extractFields.d.ts +85 -0
- package/lib/utils/extractFields.js +231 -0
- package/lib/utils/extractFields.js.map +1 -0
- package/lib/utils/index.d.ts +13 -0
- package/lib/utils/index.js +30 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/normalizeForNestia.d.ts +34 -0
- package/lib/utils/normalizeForNestia.js +133 -0
- package/lib/utils/normalizeForNestia.js.map +1 -0
- package/lib/utils/resourcePlan.d.ts +39 -0
- package/lib/utils/resourcePlan.js +95 -0
- package/lib/utils/resourcePlan.js.map +1 -0
- package/lib/utils/sliceDocument.d.ts +17 -0
- package/lib/utils/sliceDocument.js +114 -0
- package/lib/utils/sliceDocument.js.map +1 -0
- package/lib/utils/toEndpoints.d.ts +90 -0
- package/lib/utils/toEndpoints.js +227 -0
- package/lib/utils/toEndpoints.js.map +1 -0
- package/lib/verify/runWorkflows.d.ts +25 -0
- package/lib/verify/runWorkflows.js +366 -0
- package/lib/verify/runWorkflows.js.map +1 -0
- package/lib/verify/workflows.d.ts +53 -0
- package/lib/verify/workflows.js +107 -0
- package/lib/verify/workflows.js.map +1 -0
- package/package.json +82 -0
- package/prompts/AUTOVIEW_RENDER.md +398 -0
- package/prompts/AUTOVIEW_REVIEW.md +60 -0
- package/prompts/AUTOVIEW_SDK_STUDY.md +89 -0
- package/src/AutoViewAgent.ts +222 -0
- package/src/agent/emitMcpServer.integration.test.ts +168 -0
- package/src/agent/emitMcpServer.test.ts +51 -0
- package/src/agent/emitMcpServer.ts +178 -0
- package/src/agent/emitReport.ts +117 -0
- package/src/agent/toolSurface.test.ts +243 -0
- package/src/agent/toolSurface.ts +501 -0
- package/src/agent/verifyAgentTasks.test.ts +106 -0
- package/src/agent/verifyAgentTasks.ts +171 -0
- package/src/cli/main.ts +363 -0
- package/src/compiler/AutoViewInterfaceCompiler.ts +69 -0
- package/src/constants/AutoViewFrontendTemplate.ts +42 -0
- package/src/constants/AutoViewSystemPromptConstant.ts +6 -0
- package/src/context/IAutoViewAgentContext.ts +84 -0
- package/src/fromSwagger.test.ts +269 -0
- package/src/fromSwagger.ts +500 -0
- package/src/generateDeterministic.test.ts +39 -0
- package/src/generateDeterministic.ts +77 -0
- package/src/index.ts +30 -0
- package/src/orchestrate/orchestrateAutoView.ts +590 -0
- package/src/orchestrate/orchestrateAutoViewProductPlan.ts +121 -0
- package/src/orchestrate/orchestrateAutoViewRender.ts +1117 -0
- package/src/orchestrate/orchestrateAutoViewRenderDeterministic.ts +101 -0
- package/src/orchestrate/orchestrateAutoViewReview.ts +272 -0
- package/src/orchestrate/orchestrateAutoViewScaffold.ts +627 -0
- package/src/orchestrate/orchestrateAutoViewSdkStudy.ts +90 -0
- package/src/orchestrate/renderNavTs.test.ts +74 -0
- package/src/orchestrate/structures/IAutoViewProductPlan.ts +119 -0
- package/src/orchestrate/structures/IAutoViewProductPlanApplication.ts +41 -0
- package/src/orchestrate/structures/IAutoViewRenderApplication.ts +40 -0
- package/src/orchestrate/structures/IAutoViewReviewApplication.ts +42 -0
- package/src/orchestrate/structures/IAutoViewSdkMap.ts +72 -0
- package/src/orchestrate/structures/IAutoViewSdkStudyApplication.ts +40 -0
- package/src/orchestrate/utils/HistoryMessage.ts +41 -0
- package/src/orchestrate/utils/auditFrontendRuntime.test.ts +18 -0
- package/src/orchestrate/utils/auditFrontendRuntime.ts +454 -0
- package/src/orchestrate/utils/buildDeterministicPlan.test.ts +170 -0
- package/src/orchestrate/utils/buildDeterministicPlan.ts +289 -0
- package/src/orchestrate/utils/buildDeterministicSdkMap.test.ts +90 -0
- package/src/orchestrate/utils/buildDeterministicSdkMap.ts +169 -0
- package/src/orchestrate/utils/cacheNodeModules.ts +136 -0
- package/src/orchestrate/utils/describeEndpointPropsShape.test.ts +86 -0
- package/src/orchestrate/utils/describeEndpointPropsShape.ts +202 -0
- package/src/orchestrate/utils/describeEndpointRequestBodyShape.test.ts +87 -0
- package/src/orchestrate/utils/describeEndpointRequestBodyShape.ts +31 -0
- package/src/orchestrate/utils/describeEndpointResponseShape.test.ts +70 -0
- package/src/orchestrate/utils/describeEndpointResponseShape.ts +32 -0
- package/src/orchestrate/utils/executeCachedBatch.ts +59 -0
- package/src/orchestrate/utils/loadShoppingFixture.ts +52 -0
- package/src/orchestrate/utils/normalizeProductPlanPaths.ts +92 -0
- package/src/orchestrate/utils/renderJsonSchema.test.ts +162 -0
- package/src/orchestrate/utils/renderJsonSchema.ts +133 -0
- package/src/orchestrate/utils/renderResourcePage.test.ts +468 -0
- package/src/orchestrate/utils/renderResourcePage.ts +1624 -0
- package/src/orchestrate/utils/validateFrontendTypecheck.test.ts +32 -0
- package/src/orchestrate/utils/validateFrontendTypecheck.ts +335 -0
- package/src/preview/renderPreview.ts +273 -0
- package/src/typings/compiler.ts +47 -0
- package/src/typings/events.ts +155 -0
- package/src/typings/index.ts +10 -0
- package/src/typings/misc.ts +93 -0
- package/src/utils/ArrayUtil.ts +16 -0
- package/src/utils/StringUtil.ts +29 -0
- package/src/utils/classifyEndpoints.test.ts +86 -0
- package/src/utils/classifyEndpoints.ts +291 -0
- package/src/utils/endpointFilter.test.ts +50 -0
- package/src/utils/endpointFilter.ts +0 -0
- package/src/utils/extractFields.test.ts +82 -0
- package/src/utils/extractFields.ts +306 -0
- package/src/utils/index.ts +13 -0
- package/src/utils/normalizeForNestia.test.ts +93 -0
- package/src/utils/normalizeForNestia.ts +139 -0
- package/src/utils/resourcePlan.test.ts +104 -0
- package/src/utils/resourcePlan.ts +180 -0
- package/src/utils/sliceDocument.test.ts +85 -0
- package/src/utils/sliceDocument.ts +119 -0
- package/src/utils/toEndpoints.test.ts +251 -0
- package/src/utils/toEndpoints.ts +343 -0
- package/src/verify/runWorkflows.ts +403 -0
- package/src/verify/workflows.test.ts +117 -0
- package/src/verify/workflows.ts +154 -0
- package/template/CLAUDE.md +140 -0
- package/template/Dockerfile +31 -0
- package/template/PROMPT.md +80 -0
- package/template/SANDBOX.md +70 -0
- package/template/app/api/health/route.ts +10 -0
- package/template/app/globals.css +97 -0
- package/template/app/layout.tsx +30 -0
- package/template/app/page.tsx +19 -0
- package/template/components/AppShell.tsx +114 -0
- package/template/components/auto/CatalogGrid.tsx +159 -0
- package/template/components/auto/ConfirmButton.tsx +67 -0
- package/template/components/auto/EmbeddedCollection.tsx +144 -0
- package/template/components/auto/ResourceDashboard.tsx +104 -0
- package/template/components/auto/ResourceDetail.tsx +93 -0
- package/template/components/auto/ResourceForm.tsx +235 -0
- package/template/components/auto/ResourceIcon.tsx +88 -0
- package/template/components/auto/ResourceLanding.tsx +155 -0
- package/template/components/auto/ResourceTable.tsx +223 -0
- package/template/components/auto/formatValue.tsx +186 -0
- package/template/components/auto/types.ts +42 -0
- package/template/components/ui/badge.tsx +40 -0
- package/template/components/ui/button.tsx +57 -0
- package/template/components/ui/card.tsx +86 -0
- package/template/components/ui/dialog.tsx +119 -0
- package/template/components/ui/input.tsx +23 -0
- package/template/components/ui/label.tsx +24 -0
- package/template/components/ui/pagination.tsx +117 -0
- package/template/components/ui/select.tsx +92 -0
- package/template/components/ui/sheet.tsx +135 -0
- package/template/components/ui/skeleton.tsx +15 -0
- package/template/components/ui/table.tsx +120 -0
- package/template/components/ui/tabs.tsx +55 -0
- package/template/lib/utils.ts +35 -0
- package/template/next.config.mjs +52 -0
- package/template/package.json +46 -0
- package/template/postcss.config.js +6 -0
- package/template/scripts/start-shopping-backend.sh +56 -0
- package/template/tailwind.config.ts +96 -0
- package/template/tsconfig.json +29 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
export const AutoViewFrontendTemplate: Record<string, string> = {
|
|
3
|
+
"CLAUDE.md": "# Frontend Generation Guide\n\n> Sourced from\n> [`samchon/shopping/packages/frontend/CLAUDE.md`](https://github.com/samchon/shopping/blob/master/packages/frontend/CLAUDE.md).\n>\n> AutoBE bundles this guide into every generated project so that a coding\n> agent (e.g. Codex CLI, Claude Code, Cursor) can produce a Next.js + shadcn/ui\n> frontend on top of the AutoBE-generated SDK without further instruction.\n\n## Goal\nThis project should produce a frontend that understands the SDK well.\n\nDo not let raw SDK shapes take over the UI.\n\n- Keep SDK-specific code in an adapter layer.\n- Let the UI depend on normalized domain models and hooks.\n\n## Stack\nUse a fixed base unless the user explicitly wants something else.\n\n- Use `TypeScript + Next.js + shadcn/ui` unless the user approves another stack.\n- Use environment variables for the API host.\n- Default API host: `http://127.0.0.1:37001`.\n- Add libraries only when they solve a real problem.\n\n## Start\nBefore designing screens, make the SDK surface clear.\n\n- Scaffold the app.\n- Install the SDK.\n- Read `../api/src/**/*.ts` files carefully — the SDK was generated by AutoBE\n and lives under `src/api/` in this same archive.\n- Read the comments too.\n- Treat code, types, and comments as the source of truth.\n- Map the main APIs, DTOs, and constraints before designing the UI.\n\n## Account Guidance\nIf the AutoBE backend exposes seed/operator accounts, document them in\n`wiki/` and reuse them anywhere the login flow is introduced. When the\nbackend has no fixed accounts, expose membership signup as the first-class\nentry point.\n\n## Design\nThe code structure should keep replacement cost low if the SDK changes later.\n\n- Keep SDK code in a dedicated adapter layer (`src/lib/api/`).\n- Do not spread SDK types across screens and components.\n- Explain any non-default choice for routing, state, fetching, styling, forms,\n testing, or browser automation.\n\n## Product\nRead the SDK broadly.\n\nDo not turn every endpoint into a feature. Prefer a clear product over full\nendpoint coverage.\n\n- Do not force every API into the UI.\n- Leave out APIs that are redundant, diagnostic, cluttering, or harmful to the\n main flow.\n- Note intentional omissions in `wiki/`.\n- Do not invent features the SDK does not support.\n- Handle loading, empty, error, retry, and invalidation states.\n- Finish the main user flows before adding secondary controls.\n\n## Visual Style\nThe default direction is a simple prototype-first UI.\n\nIt is only a default. If the user gives a different direction, or if the\nexisting product style is already clear, follow that instead.\n\n- The UI must work well on mobile, tablet, and desktop.\n- Start from real UI parts such as lists, tables, forms, detail views,\n dialogs, and pagination.\n- Keep the layout readable and content-first.\n- Avoid decorative choices that hurt clarity or usability.\n\n## Workflow\nDocs and helper commands should follow the code instead of drifting away from\nit.\n\n- Keep `wiki/` aligned with the code.\n- Update docs when architecture, package choices, user flows, or omissions\n change.\n- If a useful project command does not exist yet, create it before relying on\n it.\n\n## Testing\nTesting should prove that the rendered product still works.\n\nFor frontend-only work, keep the test program focused on the frontend itself.\nDo not boot the backend, judge backend health, or let CI drift into server\nchecks.\n\nThe SDK already supports simulation through the connection object. When\n`simulate: true` is set, the SDK returns simulated responses instead of\ncalling the real backend. For frontend tests, treat this as API mocking at\nthe SDK boundary.\n\n```ts\nconst connection: IConnection = {\n host: \"http://127.0.0.1:...\",\n simulate: true,\n};\n```\n\n- If the repo does not have a suitable test stack yet, add one.\n- Keep a browser-first test program for the main user flows.\n- Prefer Playwright for end-to-end and UI review work unless the user wants\n something else.\n- Do not add backend health, startup, or server-state checks to the frontend\n test program or its GitHub Actions workflow.\n- If integration testing is needed, keep it as a separate test program.\n- Keep local test commands and GitHub Actions aligned when the test setup\n changes.\n\n## UI Review\nUI work is not done when the code compiles.\n\nIt is done after the flow has been used and checked.\n\n- Run the flow yourself.\n- Prefer direct browser interaction.\n- Install browser automation before falling back.\n- Check the UI at mobile, tablet, and desktop sizes.\n- Verify that controls cause observable changes.\n- Verify that search, sort, pagination, page size, toggles, dialogs, and forms\n actually work when present.\n- Do one final pass for layout and copy before calling the work done.\n- Fall back to screenshots or raw API checks only when browser automation is\n not available.\n\n## Done\nDone means the product works, not just that files were written.\n\n- The app starts.\n- Core flows work.\n- The UI is coherent.\n- The docs match the code.\n- The tests match the code.\n- If an SDK feature makes the product worse, simplify it or leave it out.\n",
|
|
4
|
+
"Dockerfile": "# Next.js standalone build optimized for the in-house Sandbox platform.\n# - Multi-stage so the runtime layer carries only `.next/standalone/` and\n# `public/`, not the full `node_modules/`.\n# - Listens on the port supplied by `PORT` (defaults to 3000) so the sandbox\n# ingress can route to it.\n# - The build expects `output: \"standalone\"` in `next.config.mjs`. The\n# `PROMPT.md` instructs the coding agent to set that flag.\n\nFROM node:20-alpine AS deps\nWORKDIR /app\nCOPY package.json package-lock.json* ./\nRUN npm ci --no-audit --no-fund\n\nFROM node:20-alpine AS build\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY . .\nRUN npm run build\n\nFROM node:20-alpine AS runtime\nWORKDIR /app\nENV NODE_ENV=production\nENV PORT=3000\nENV HOSTNAME=0.0.0.0\nCOPY --from=build /app/public ./public\nCOPY --from=build /app/.next/standalone ./\nCOPY --from=build /app/.next/static ./.next/static\nEXPOSE 3000\nHEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \\\n CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:${PORT}/api/health || exit 1\nCMD [\"node\", \"server.js\"]\n",
|
|
5
|
+
"PROMPT.md": "# Frontend Generation — Master Prompt\n\n> Paste this entire file into Codex CLI (or any coding agent) as the opening\n> message. The agent will read the SDK, follow `frontend/CLAUDE.md`, and\n> produce a runnable Next.js + shadcn/ui application.\n\nYou are building a frontend application on top of a TypeScript SDK that\nAutoBE has just generated. Your job is to produce a product, not a catalog.\n\n## Inputs in this archive\n\n- `frontend/CLAUDE.md` — your operating manual. Follow it strictly.\n- `frontend/SANDBOX.md` — deployment constraints for the in-house Sandbox\n platform. **Mandatory** if the operator plans to deploy this app.\n- `frontend/Dockerfile` — keep this file. The Sandbox build pipeline expects\n it as-is (Next.js standalone, node 20-alpine, healthcheck on `/api/health`).\n- `src/api/**` — the SDK that AutoBE generated. Read every file, including\n the JSDoc comments, before you write a single UI component.\n- `docs/openapi.json` — the OpenAPI document the SDK was generated from. Use\n it only as a cross-reference; the SDK is the source of truth.\n\n## Task\n\n1. **Study** the SDK. Build a mental map of the resources, actors (e.g.\n customer, seller, administrator, guest), and end-to-end user journeys\n the SDK supports. Write your findings into `frontend/wiki/sdk-map.md`.\n2. **Plan the product**, not the endpoint catalog. Decide which screens\n exist (e.g. Catalog, Cart, Order detail, Admin sales) and what each\n screen does. Each screen typically composes several SDK calls into one\n coherent flow. Record the plan in `frontend/wiki/product-plan.md`,\n including intentional omissions.\n3. **Scaffold** a Next.js 15 + shadcn/ui project inside `frontend/`. Wire\n the SDK through an adapter layer (`frontend/src/lib/api/`). Use\n `simulate: true` on the SDK connection by default so the app stays\n browsable without a live backend. **Set `output: \"standalone\"`** in\n `next.config.mjs` — the Sandbox Dockerfile depends on this. **Add the\n healthcheck route** `app/api/health/route.ts` that returns\n `Response.json({ status: \"ok\" })` for `GET`.\n4. **Implement** the planned screens. Use shadcn/ui primitives (Button,\n Card, Table, Dialog, Sheet, Tabs, Form, Select, Pagination, ...) and\n Tailwind utilities. Components must work on mobile, tablet, and desktop.\n Handle loading, empty, error, retry, and invalidation states.\n5. **Verify**. Run the dev server, click through every main flow yourself,\n and capture screenshots into `frontend/wiki/screenshots/`. If a flow is\n awkward because the SDK is awkward, note it in\n `frontend/wiki/sdk-feedback.md` — this feedback is the whole reason the\n frontend exists.\n\n## Quality bar\n\n- The app boots cleanly with `npm run dev`.\n- The home page is a real landing surface, not a list of API endpoints.\n- Every navigation entry leads to a screen that does something useful.\n- Forms validate on the client and surface server errors gracefully.\n- Lists support pagination, sort, and any filters the SDK exposes.\n- `npm run lint` and `npm run typecheck` both pass.\n- `docker build .` succeeds, and `docker run -p 3000:3000 <image>` exposes\n `GET /api/health` returning `200 { status: \"ok\" }`. The Sandbox build\n pipeline runs the same `docker build` so this is the deploy gate.\n- `README.md` declares the Sandbox spec (Port, Healthcheck, Dockerfile,\n Env vars) exactly as `frontend/SANDBOX.md` requires.\n\n## Deployment target\n\nThis app deploys to the in-house Sandbox platform via Slack\n`/sandbox-server-infra-setup`. **Do not** generate `vercel.json`,\n`netlify.toml`, or any other public-PaaS config — the operator runs the\nSlack command on a private GitHub repo and Sandbox handles the rest.\n`frontend/SANDBOX.md` lists the exact artifacts the platform expects.\n\n## What \"done\" looks like\n\nA non-technical user can open the dev server URL, log in (or use the\n`simulate: true` default), browse the main resources, perform the primary\nCRUD or transactional flows, and form a real opinion about whether the API\nunderneath is well-designed. If they cannot — if the UI exposes the API's\nawkwardness — that is valuable signal, not a failure. Write it down in\n`frontend/wiki/sdk-feedback.md`.\n\nStart by reading `frontend/CLAUDE.md` in full, then read `src/api/`.\n",
|
|
6
|
+
"SANDBOX.md": "# Sandbox Deployment Guide\n\nThis frontend is generated to deploy on the in-house **Sandbox platform**.\nVercel and other public PaaS are **out of scope** — do not introduce them.\n\n## Required artifacts in the repo\n\nThe coding agent must produce all of the following at the **repo root** of\nthe frontend project. The sandbox `/sandbox-server-infra-setup` command\nreads these to provision the service.\n\n| Path | Contents |\n| ------------------- | ------------------------------------------------------------------------ |\n| `Dockerfile` | Multi-stage Next.js standalone build. The template ships one — keep it. |\n| `next.config.mjs` | Must include `output: \"standalone\"`. |\n| `app/api/health/route.ts` | `GET` returning `200 { status: \"ok\" }` — wired into the Docker `HEALTHCHECK`. |\n| `README.md` | Lists port, healthcheck path, Dockerfile spec, and env vars (below). |\n\n## README spec — required sections\n\nThe sandbox setup reads these from the README:\n\n```markdown\n## Sandbox\n\n- Port: 3000\n- Healthcheck: `GET /api/health`\n- Dockerfile: ./Dockerfile (Next.js standalone, node 20-alpine)\n- Env vars (Secret Manager):\n - `NEXT_PUBLIC_API_HOST` — AutoBE backend base URL inside sandbox\n - `MONGO_URL` — autowired by sandbox; do not set manually\n - (anything else this project introduces)\n```\n\n## Repo layout — required\n\n- **Private repo**, not internal.\n- `dev` environment ↔ `develop` branch.\n- `prod` environment ↔ `main` branch (requires Platform + Security review).\n- If the frontend lives in a sub-folder of a monorepo, the agent must\n document that path so the sandbox setup picks the right `Dockerfile`.\n\n## Deploy procedure (operator)\n\n1. Push the repo (private). Verify `develop` branch exists.\n2. In Slack `#platform-team`, run `/sandbox-server-infra-setup`.\n3. Select the repo, environment (`dev` first), and enter the reason for\n the request.\n4. Secret Manager is auto-wired; set values according to the env var list\n in the README before promoting to `prod`.\n5. For `prod` rollout, request Platform + Security review.\n\n## What the coding agent should NOT do\n\n- Do not generate `vercel.json`, `netlify.toml`, or any other public PaaS\n config.\n- Do not embed secrets in source. Reference them through `process.env.*`\n and document them in the README's Sandbox section.\n- Do not change the Dockerfile's `PORT` / `HOSTNAME` / `EXPOSE 3000`\n without also updating the README.\n- Do not remove the `/api/health` route — the Dockerfile `HEALTHCHECK`\n depends on it.\n\n## Why this exists\n\nThe internal Sandbox platform replaces hand-rolled Kubernetes wiring\n(yml + Dockerfile + chart + secrets) with a single Slack command that\nprovisions everything in 5–10 minutes from a standardized template. The\ntemplate assumes the four artifacts above. Missing any one of them turns\na 5-minute deploy back into a half-day infra ticket.\n",
|
|
7
|
+
"app/api/health/route.ts": "// Sandbox HEALTHCHECK endpoint. The Dockerfile invokes\n// `GET /api/health` every 30s — return `{ status: \"ok\" }` so the container\n// is marked healthy in the sandbox dashboard. Do not remove or rename.\n\nexport const dynamic = \"force-static\";\nexport const revalidate = false;\n\nexport function GET(): Response {\n return Response.json({ status: \"ok\" });\n}\n",
|
|
8
|
+
"app/globals.css": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n/*\n * SpaceX theme — pure black, white, near-zero radius, industrial.\n * Dark IS the default (SpaceX has no light mode), so the values live in\n * `:root`; `.dark` mirrors them. Monochrome on purpose: color is reserved\n * for status (success/warning/destructive) so state reads instantly against\n * the black. Every Resource* component reads these tokens — re-skinning the\n * whole generated app is a single edit here.\n */\n@layer base {\n :root {\n --background: 0 0% 0%;\n --foreground: 0 0% 100%;\n\n --card: 0 0% 5%;\n --card-foreground: 0 0% 100%;\n --popover: 0 0% 5%;\n --popover-foreground: 0 0% 100%;\n\n /* Brand: white-on-black CTA, SpaceX \"ORDER NOW\" style. */\n --primary: 0 0% 100%;\n --primary-foreground: 0 0% 0%;\n\n --secondary: 0 0% 12%;\n --secondary-foreground: 0 0% 100%;\n --muted: 0 0% 11%;\n --muted-foreground: 0 0% 60%;\n\n /* Accent = subtle lift on hover / active nav, still monochrome. */\n --accent: 0 0% 14%;\n --accent-foreground: 0 0% 100%;\n\n /* Semantic status colors — the only color in the system. */\n --success: 145 63% 45%;\n --success-foreground: 0 0% 0%;\n --warning: 38 92% 55%;\n --warning-foreground: 0 0% 0%;\n --destructive: 0 72% 52%;\n --destructive-foreground: 0 0% 100%;\n\n --border: 0 0% 16%;\n --input: 0 0% 18%;\n --ring: 0 0% 80%;\n\n --radius: 0.125rem;\n }\n\n .dark {\n --background: 0 0% 0%;\n --foreground: 0 0% 100%;\n\n --card: 0 0% 5%;\n --card-foreground: 0 0% 100%;\n --popover: 0 0% 5%;\n --popover-foreground: 0 0% 100%;\n\n --primary: 0 0% 100%;\n --primary-foreground: 0 0% 0%;\n\n --secondary: 0 0% 12%;\n --secondary-foreground: 0 0% 100%;\n --muted: 0 0% 11%;\n --muted-foreground: 0 0% 60%;\n\n --accent: 0 0% 14%;\n --accent-foreground: 0 0% 100%;\n\n --success: 145 63% 45%;\n --success-foreground: 0 0% 0%;\n --warning: 38 92% 55%;\n --warning-foreground: 0 0% 0%;\n --destructive: 0 72% 52%;\n --destructive-foreground: 0 0% 100%;\n\n --border: 0 0% 16%;\n --input: 0 0% 18%;\n --ring: 0 0% 80%;\n }\n}\n\n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n font-feature-settings: \"tnum\" 1, \"calt\" 1;\n -webkit-font-smoothing: antialiased;\n }\n /* Headings carry the industrial, wide-tracked SpaceX voice. */\n h1, h2 {\n @apply uppercase tracking-wide;\n }\n}\n",
|
|
9
|
+
"app/layout.tsx": "import type { Metadata } from \"next\";\nimport { Archivo } from \"next/font/google\";\nimport \"./globals.css\";\n\n// Archivo — a grotesque with the industrial, slightly condensed character of\n// SpaceX's D-DIN. Carries the whole UI; headings go uppercase + tracked in CSS.\nconst archivo = Archivo({\n subsets: [\"latin\"],\n variable: \"--font-sans\",\n display: \"swap\",\n});\n\nexport const metadata: Metadata = {\n title: \"AutoBE Frontend\",\n description: \"Generated by the AutoView agent on top of an AutoBE SDK.\",\n};\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}) {\n return (\n <html lang=\"en\" className={`dark ${archivo.variable}`} suppressHydrationWarning>\n <body className=\"min-h-screen bg-background font-sans antialiased\">\n {children}\n </body>\n </html>\n );\n}\n",
|
|
10
|
+
"app/page.tsx": "// Placeholder landing page. The AutoView agent (or a coding agent following\n// `frontend/PROMPT.md`) replaces this file with the real entry point based\n// on the SDK's actor flow — e.g. a storefront for shopping APIs, a\n// dashboard for admin APIs, etc.\n\nexport default function HomePage() {\n return (\n <main className=\"container mx-auto py-16\">\n <h1 className=\"text-3xl font-bold tracking-tight\">AutoBE Frontend</h1>\n <p className=\"mt-3 text-muted-foreground\">\n This app has not been customized yet. Follow{\" \"}\n <code className=\"rounded bg-muted px-1.5 py-0.5 font-mono text-sm\">\n frontend/PROMPT.md\n </code>{\" \"}\n to scaffold the product on top of the generated SDK.\n </p>\n </main>\n );\n}\n",
|
|
11
|
+
"components/AppShell.tsx": "\"use client\";\n\nimport { Home } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport * as React from \"react\";\n\nimport { ResourceIcon } from \"@/components/auto/ResourceIcon\";\nimport { cn } from \"@/lib/utils\";\nimport { APP_NAME, NAV, type NavItem } from \"@/src/lib/nav\";\n\n/**\n * Application chrome shared by every generated page: a persistent left sidebar\n * with the resource navigation (grouped) and a slim top header. Turns the\n * generated pages from bare centered columns into a real admin product. Static\n * and swagger-agnostic — the nav itself is generated into `@/src/lib/nav`.\n */\nexport function AppShell({ children }: { children: React.ReactNode }) {\n const pathname = usePathname();\n const groups = groupNav(NAV);\n\n const isActive = (href: string) =>\n pathname === href || pathname.startsWith(`${href}/`);\n\n return (\n <div className=\"min-h-screen bg-muted/20\">\n <div className=\"flex\">\n <aside className=\"sticky top-0 hidden h-screen w-60 shrink-0 flex-col border-r bg-background md:flex\">\n <Link\n href=\"/\"\n className=\"flex h-14 items-center gap-2.5 border-b px-5\"\n >\n <span className=\"flex h-6 w-6 items-center justify-center rounded bg-primary text-xs font-bold text-primary-foreground\">\n {APP_NAME.charAt(0).toUpperCase()}\n </span>\n <span className=\"truncate text-sm font-semibold tracking-tight\">\n {APP_NAME}\n </span>\n </Link>\n <nav className=\"flex-1 space-y-5 overflow-y-auto p-3\">\n <Link href=\"/\" className={navLinkClass(pathname === \"/\")}>\n <Home className=\"h-4 w-4 shrink-0 opacity-70\" />\n <span className=\"truncate\">Home</span>\n </Link>\n {groups.map(([group, items]) => (\n <div key={group}>\n <p className=\"mb-1 px-3 text-[11px] font-medium uppercase tracking-wider text-muted-foreground\">\n {group}\n </p>\n <div className=\"space-y-0.5\">\n {items.map((item) => (\n <Link\n key={item.href}\n href={item.href}\n className={navLinkClass(isActive(item.href))}\n >\n <ResourceIcon\n name={item.href}\n className=\"h-4 w-4 shrink-0 opacity-70\"\n />\n <span className=\"truncate\">{item.title}</span>\n </Link>\n ))}\n </div>\n </div>\n ))}\n </nav>\n <div className=\"border-t px-4 py-3 text-[11px] text-muted-foreground\">\n Generated by AutoView\n </div>\n </aside>\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <header className=\"sticky top-0 z-10 flex h-14 items-center gap-3 border-b bg-background/80 px-5 backdrop-blur md:px-8\">\n <Link href=\"/\" className=\"text-sm font-semibold md:hidden\">\n {APP_NAME}\n </Link>\n <nav className=\"flex gap-1 overflow-x-auto md:hidden\">\n {NAV.slice(0, 6).map((item) => (\n <Link\n key={item.href}\n href={item.href}\n className=\"whitespace-nowrap rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted\"\n >\n {item.title}\n </Link>\n ))}\n </nav>\n </header>\n <main className=\"min-w-0 flex-1\">{children}</main>\n </div>\n </div>\n </div>\n );\n}\n\nfunction navLinkClass(active: boolean): string {\n return cn(\n \"flex items-center gap-2.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors\",\n active\n ? \"bg-muted text-foreground\"\n : \"text-muted-foreground hover:bg-muted/60 hover:text-foreground\",\n );\n}\n\nfunction groupNav(items: NavItem[]): Array<[string, NavItem[]]> {\n const map = new Map<string, NavItem[]>();\n for (const item of items) {\n const bucket = map.get(item.group) ?? [];\n bucket.push(item);\n map.set(item.group, bucket);\n }\n return [...map.entries()];\n}\n",
|
|
12
|
+
"components/auto/CatalogGrid.tsx": "\"use client\";\n\nimport Link from \"next/link\";\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\n\nimport { formatCell } from \"./formatValue\";\nimport type { ColumnSpec } from \"./types\";\n\n/** Which fields drive a catalog card, baked by the generator from the schema. */\nexport interface CatalogSpec {\n /** Field holding the image URL. */\n imageField: string;\n /** True when {@link imageField} is a `string[]` — use its first element. */\n imageIsArray: boolean;\n /** Field holding the card's title. */\n titleField: string;\n /** A few secondary columns shown under the title. */\n meta: ColumnSpec[];\n}\n\nexport interface CatalogGridProps {\n title: string;\n description?: string;\n spec: CatalogSpec;\n rows: readonly unknown[];\n loading: boolean;\n error?: string | null;\n onRetry?: () => void;\n rowHref?: (row: Record<string, unknown>) => string | null;\n emptyHint?: string;\n}\n\nfunction imageUrl(row: Record<string, unknown>, spec: CatalogSpec): string | null {\n const v = row[spec.imageField];\n const raw = spec.imageIsArray ? (Array.isArray(v) ? v[0] : undefined) : v;\n return typeof raw === \"string\" && raw.length > 0 ? raw : null;\n}\n\n/**\n * Universal catalog screen — a grid of image cards, one per record. Chosen over\n * the table when the row schema carries an image + a title (a product, a photo,\n * a profile). Schema-driven: the image / title / meta fields are decided by the\n * generator from the element type, so any image-bearing collection reads as a\n * gallery instead of a dense table. Same loading / error / empty / data states\n * as the table.\n */\nexport function CatalogGrid(props: CatalogGridProps) {\n const { title, description, spec, loading, error, onRetry, rowHref, emptyHint } =\n props;\n const rows = Array.isArray(props.rows) ? props.rows : [];\n\n const body = (() => {\n if (error !== null && error !== undefined) {\n return (\n <Card className=\"border-destructive/40 bg-destructive/5 p-5\">\n <p className=\"text-sm text-destructive\">{error}</p>\n {onRetry ? (\n <Button variant=\"outline\" size=\"sm\" className=\"mt-3\" onClick={onRetry}>\n Retry\n </Button>\n ) : null}\n </Card>\n );\n }\n if (loading) {\n return (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {Array.from({ length: 8 }).map((_, i) => (\n <div key={i} className=\"space-y-2\">\n <Skeleton className=\"aspect-[4/3] w-full\" />\n <Skeleton className=\"h-4 w-2/3\" />\n </div>\n ))}\n </div>\n );\n }\n if (rows.length === 0) {\n return (\n <Card className=\"p-6\">\n <p className=\"text-sm text-muted-foreground\">\n {emptyHint ?? \"The list came back empty.\"}\n </p>\n </Card>\n );\n }\n return (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {rows.map((raw, i) => {\n const row = (raw ?? {}) as Record<string, unknown>;\n const img = imageUrl(row, spec);\n const href = rowHref ? rowHref(row) : null;\n const card = (\n <Card className=\"h-full overflow-hidden transition-colors hover:border-primary/50\">\n <div className=\"aspect-[4/3] bg-muted\">\n {img ? (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={img}\n alt=\"\"\n loading=\"lazy\"\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div className=\"flex h-full items-center justify-center text-muted-foreground\">\n —\n </div>\n )}\n </div>\n <CardContent className=\"p-3\">\n <p className=\"truncate text-sm font-medium\">\n {String(row[spec.titleField] ?? \"—\")}\n </p>\n {spec.meta.length > 0 ? (\n <div className=\"mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground\">\n {spec.meta.map((c) => (\n <span key={c.name} className=\"inline-flex items-center\">\n {formatCell(row[c.name], c)}\n </span>\n ))}\n </div>\n ) : null}\n </CardContent>\n </Card>\n );\n return href ? (\n <Link key={i} href={href} className=\"block\">\n {card}\n </Link>\n ) : (\n <div key={i}>{card}</div>\n );\n })}\n </div>\n );\n })();\n\n return (\n <div className=\"px-5 py-6 md:px-8\">\n <div className=\"mb-5 flex items-end justify-between gap-4\">\n <div>\n <h1 className=\"text-xl font-semibold tracking-tight\">{title}</h1>\n {description ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">{description}</p>\n ) : null}\n </div>\n {!loading && !error ? (\n <span className=\"hidden text-xs text-muted-foreground sm:inline\">\n {rows.length} item{rows.length === 1 ? \"\" : \"s\"}\n </span>\n ) : null}\n </div>\n {body}\n </div>\n );\n}\n",
|
|
13
|
+
"components/auto/ConfirmButton.tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"@/components/ui/dialog\";\n\nexport interface ConfirmButtonProps {\n label: string;\n onConfirm: () => void;\n disabled?: boolean;\n title?: string;\n description?: string;\n}\n\n/**\n * A destructive button that asks for confirmation before firing. Used for\n * DELETE actions so a single click cannot irreversibly destroy a record — the\n * generated detail page wires `onConfirm` to the SDK delete call.\n */\nexport function ConfirmButton(props: ConfirmButtonProps) {\n const { label, onConfirm, disabled, title, description } = props;\n const [open, setOpen] = React.useState(false);\n return (\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogTrigger asChild>\n <Button variant=\"destructive\" size=\"sm\" disabled={disabled}>\n {label}\n </Button>\n </DialogTrigger>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>{title ?? \"Are you sure?\"}</DialogTitle>\n <DialogDescription>\n {description ?? \"This action cannot be undone.\"}\n </DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <DialogClose asChild>\n <Button variant=\"outline\" size=\"sm\">\n Cancel\n </Button>\n </DialogClose>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n onClick={() => {\n setOpen(false);\n onConfirm();\n }}\n >\n {label}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n",
|
|
14
|
+
"components/auto/EmbeddedCollection.tsx": "\"use client\";\n\nimport Link from \"next/link\";\nimport * as React from \"react\";\n\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/components/ui/table\";\nimport { humanizeLabel } from \"@/lib/utils\";\n\nimport { formatCell } from \"./formatValue\";\nimport type { ColumnSpec } from \"./types\";\n\n/** Rows shown inline; the full set lives on the child's own list screen. */\nconst PREVIEW_LIMIT = 5;\n/** Columns shown inline — a detail embed stays compact, not the full table. */\nconst PREVIEW_COLUMNS = 5;\n\nexport interface EmbeddedCollectionProps {\n title: string;\n columns: ColumnSpec[];\n /** Fetch + extract the child rows. Supplied by the generated detail page. */\n load: () => Promise<readonly unknown[]>;\n /** Link to the child's full list screen (\"View all\"). */\n href?: string;\n}\n\n/**\n * A child collection rendered inline inside a parent's detail page — the master\n * half of a master-detail view. Fetches its own rows, shows a compact preview\n * table, and links to the full list screen. This is how a nested resource reads\n * as part of its parent (an order's items, a folder's files) instead of a\n * separate page you have to navigate to.\n */\nexport function EmbeddedCollection(props: EmbeddedCollectionProps) {\n const { title, columns, load, href } = props;\n const [rows, setRows] = React.useState<readonly unknown[]>([]);\n const [loading, setLoading] = React.useState(true);\n const [error, setError] = React.useState<string | null>(null);\n\n React.useEffect(() => {\n let alive = true;\n setLoading(true);\n setError(null);\n load()\n .then((r) => {\n if (alive) setRows(Array.isArray(r) ? r : []);\n })\n .catch((e) => {\n if (alive) setError(e instanceof Error ? e.message : String(e));\n })\n .finally(() => {\n if (alive) setLoading(false);\n });\n return () => {\n alive = false;\n };\n }, []);\n\n const cols = columns.slice(0, PREVIEW_COLUMNS);\n const preview = rows.slice(0, PREVIEW_LIMIT);\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-3 space-y-0 py-3\">\n <CardTitle className=\"text-sm font-semibold\">\n {title}\n {!loading && !error ? (\n <span className=\"ml-2 text-xs font-normal text-muted-foreground\">\n {rows.length}\n {rows.length >= PREVIEW_LIMIT ? \"+\" : \"\"}\n </span>\n ) : null}\n </CardTitle>\n {href ? (\n <Link\n href={href}\n className=\"shrink-0 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground\"\n >\n View all →\n </Link>\n ) : null}\n </CardHeader>\n <CardContent className=\"p-0\">\n {error ? (\n <p className=\"px-5 py-4 text-sm text-destructive\">{error}</p>\n ) : loading ? (\n <div className=\"space-y-2 px-5 py-4\">\n {Array.from({ length: 3 }).map((_, i) => (\n <Skeleton key={i} className=\"h-6 w-full\" />\n ))}\n </div>\n ) : preview.length === 0 ? (\n <p className=\"px-5 py-4 text-sm text-muted-foreground\">\n No {title.toLowerCase()} yet.\n </p>\n ) : (\n <div className=\"overflow-x-auto border-t\">\n <Table>\n <TableHeader>\n <TableRow className=\"hover:bg-transparent\">\n {cols.map((c) => (\n <TableHead\n key={c.name}\n className=\"h-9 whitespace-nowrap text-xs uppercase tracking-wide text-muted-foreground\"\n >\n {humanizeLabel(c.name)}\n </TableHead>\n ))}\n </TableRow>\n </TableHeader>\n <TableBody>\n {preview.map((raw, i) => {\n const row = (raw ?? {}) as Record<string, unknown>;\n return (\n <TableRow key={i}>\n {cols.map((c) => (\n <TableCell key={c.name} className=\"max-w-xs align-top text-sm\">\n {formatCell(row[c.name], c)}\n </TableCell>\n ))}\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n",
|
|
15
|
+
"components/auto/ResourceDashboard.tsx": "\"use client\";\n\nimport Link from \"next/link\";\nimport * as React from \"react\";\n\nimport { Card } from \"@/components/ui/card\";\nimport { ResourceIcon } from \"@/components/auto/ResourceIcon\";\nimport { ResourceLinks, type LandingLink } from \"@/components/auto/ResourceLanding\";\n\nexport interface DashboardStat {\n href: string;\n title: string;\n /** Live record count for the resource; `null` while loading or on error. */\n count: number | null;\n /** Titles of the first few records, for an at-a-glance \"recent\" preview. */\n recent?: string[];\n}\n\nexport interface ResourceDashboardProps {\n title: string;\n subtitle?: string;\n /** Top hub resources surfaced as count cards. Empty → just the link grid. */\n stats: DashboardStat[];\n links: LandingLink[];\n}\n\n/**\n * Home dashboard. A strip of live count cards for the key hub resources sits\n * above the full grouped resource-link grid. Turns the bare link hub into an\n * actual operator landing — \"how much is in each resource\" at a glance — while\n * keeping navigation to every resource. Counts are fetched by the generated\n * page (one list call per stat); this component only renders.\n */\nexport function ResourceDashboard(props: ResourceDashboardProps) {\n const { title, subtitle, stats, links } = props;\n // Largest count, so each card's bar reads as magnitude relative to the others.\n const max = Math.max(1, ...stats.map((s) => s.count ?? 0));\n return (\n <div className=\"px-5 py-8 md:px-8\">\n <div className=\"mb-7\">\n <h1 className=\"text-2xl font-semibold tracking-tight\">{title}</h1>\n {subtitle ? (\n <p className=\"mt-1.5 text-sm text-muted-foreground\">{subtitle}</p>\n ) : null}\n </div>\n\n {stats.length > 0 ? (\n <div className=\"mb-9 grid gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n {stats.map((stat) => (\n <Link key={stat.href} href={stat.href} className=\"group\">\n <Card className=\"flex h-full flex-col p-4 transition-colors group-hover:border-primary/50 group-hover:bg-muted/30\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <ResourceIcon\n name={stat.href}\n className=\"h-4 w-4 shrink-0 text-muted-foreground\"\n />\n <p className=\"truncate text-xs font-medium uppercase tracking-wider text-muted-foreground\">\n {stat.title}\n </p>\n </div>\n <span className=\"shrink-0 text-muted-foreground opacity-0 transition group-hover:opacity-100\">\n →\n </span>\n </div>\n <p className=\"mt-2 text-3xl font-semibold tabular-nums\">\n {stat.count === null ? (\n <span className=\"text-muted-foreground\">—</span>\n ) : (\n stat.count.toLocaleString()\n )}\n </p>\n {stat.count !== null ? (\n <div className=\"mt-2 h-1 w-full overflow-hidden rounded-full bg-muted\">\n <div\n className=\"h-full rounded-full bg-primary/60\"\n style={{\n width: `${Math.max(4, Math.round((stat.count / max) * 100))}%`,\n }}\n />\n </div>\n ) : null}\n {stat.recent && stat.recent.length > 0 ? (\n <ul className=\"mt-3 space-y-1 border-t pt-2\">\n {stat.recent.slice(0, 2).map((r, i) => (\n <li\n key={i}\n className=\"truncate text-xs text-muted-foreground\"\n >\n {r}\n </li>\n ))}\n </ul>\n ) : null}\n </Card>\n </Link>\n ))}\n </div>\n ) : null}\n\n <ResourceLinks links={links} />\n </div>\n );\n}\n",
|
|
16
|
+
"components/auto/ResourceDetail.tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { humanizeLabel } from \"@/lib/utils\";\n\nimport { formatCell } from \"./formatValue\";\nimport type { ColumnSpec } from \"./types\";\n\nexport interface ResourceDetailProps {\n title: string;\n /** One row per field of the response type — the whole record. */\n fields: ColumnSpec[];\n /** The single record the detail endpoint returned. `unknown` so the page never casts. */\n data: unknown;\n loading: boolean;\n error?: string | null;\n onRetry?: () => void;\n /** Buttons rendered next to the title (edit link, delete, …). */\n actions?: React.ReactNode;\n}\n\n/**\n * Universal detail screen. Renders a definition list — one row per field of the\n * response type, formatted by kind. Schema-driven, so every property of the\n * record is shown, never an LLM-chosen subset.\n */\nexport function ResourceDetail(props: ResourceDetailProps) {\n const { title, fields, data, loading, error, onRetry, actions } = props;\n\n const body = (() => {\n if (error !== null && error !== undefined) {\n return (\n <Card className=\"border-destructive/40 bg-destructive/5\">\n <CardHeader>\n <CardTitle className=\"text-base text-destructive\">Couldn’t load</CardTitle>\n <CardDescription className=\"break-words\">{error}</CardDescription>\n </CardHeader>\n {onRetry ? (\n <CardContent>\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry}>\n Retry\n </Button>\n </CardContent>\n ) : null}\n </Card>\n );\n }\n if (loading || data === null || data === undefined) {\n return (\n <div className=\"space-y-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <Skeleton key={i} className=\"h-8 w-full\" />\n ))}\n </div>\n );\n }\n const record = data as Record<string, unknown>;\n return (\n <Card>\n <CardContent className=\"divide-y p-0\">\n {fields.map((f) => (\n <div key={f.name} className=\"grid grid-cols-3 gap-4 px-5 py-3\">\n <dt className=\"text-sm font-medium text-muted-foreground\">{humanizeLabel(f.name)}</dt>\n <dd className=\"col-span-2 break-words text-sm\">\n {formatCell(record[f.name], f)}\n </dd>\n </div>\n ))}\n </CardContent>\n </Card>\n );\n })();\n\n return (\n <div className=\"px-5 py-6 md:px-8\">\n <div className=\"mb-5 flex max-w-3xl flex-wrap items-center justify-between gap-3\">\n <h1 className=\"text-xl font-semibold tracking-tight\">{title}</h1>\n <div className=\"flex flex-wrap items-center gap-2\">{actions}</div>\n </div>\n <div className=\"max-w-3xl\">{body}</div>\n </div>\n );\n}\n",
|
|
17
|
+
"components/auto/ResourceForm.tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { humanizeLabel } from \"@/lib/utils\";\n\nimport type { FieldInput } from \"./types\";\n\nexport interface ResourceFormProps {\n title: string;\n description?: string;\n /** One input per field of the request body schema. */\n fields: FieldInput[];\n submitting: boolean;\n error?: string | null;\n /** Success message after a submit resolves. */\n result?: string | null;\n /** Current record values to prefill an edit form (matched by field name). */\n initialValues?: Record<string, unknown>;\n onSubmit: (values: Record<string, unknown>) => void;\n}\n\n/** HTML input type for a field, so the browser validates format natively. */\nfunction inputType(field: FieldInput): string {\n if (field.kind === \"number\") return \"number\";\n if (field.format === \"email\") return \"email\";\n if (field.format === \"uri\" || field.format === \"url\") return \"url\";\n return \"text\";\n}\n\n/** Client-side validation message for one field, or `null` when valid. */\nfunction fieldError(field: FieldInput, raw: string): string | null {\n const v = raw.trim();\n if (field.required && v === \"\") return \"Required\";\n if (v === \"\") return null;\n if (field.kind === \"number\" && Number.isNaN(Number(v))) return \"Must be a number\";\n if (field.format === \"email\" && !/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(v))\n return \"Invalid email\";\n if (\n (field.format === \"uri\" || field.format === \"url\") &&\n !/^(https?:\\/\\/|\\/)/.test(v)\n )\n return \"Invalid URL\";\n return null;\n}\n\n/** Coerce a raw string input back to the JSON type its field expects. */\nfunction coerce(raw: string, field: FieldInput): unknown {\n switch (field.kind) {\n case \"number\":\n return Number(raw);\n case \"boolean\":\n return raw === \"true\";\n case \"array\":\n return raw\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n default:\n return raw;\n }\n}\n\n/**\n * Universal create/edit form. Renders one input per field of the request body\n * schema — `enum` → select, `boolean` → true/false select, `number` → numeric\n * input, everything else → text input. Required fields are marked. On submit it\n * coerces every value back to its JSON type and hands a plain object to the\n * page, which forwards it to the SDK.\n */\nexport function ResourceForm(props: ResourceFormProps) {\n const { title, description, fields, submitting, error, result, initialValues, onSubmit } =\n props;\n const [values, setValues] = React.useState<Record<string, string>>({});\n const [errors, setErrors] = React.useState<Record<string, string>>({});\n\n // Prefill an edit form once the current record arrives — only primitive\n // fields the form actually has an input for.\n React.useEffect(() => {\n if (initialValues === undefined) return;\n const seed: Record<string, string> = {};\n for (const field of fields) {\n const v = initialValues[field.name];\n if (v === undefined || v === null || typeof v === \"object\") continue;\n seed[field.name] = String(v);\n }\n setValues(seed);\n }, [initialValues, fields]);\n\n const set = (name: string, value: string) => {\n setValues((prev) => ({ ...prev, [name]: value }));\n setErrors((prev) => (prev[name] ? { ...prev, [name]: \"\" } : prev));\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const found: Record<string, string> = {};\n for (const field of fields) {\n const msg = fieldError(field, values[field.name] ?? \"\");\n if (msg !== null) found[field.name] = msg;\n }\n if (Object.keys(found).length > 0) {\n setErrors(found);\n return;\n }\n const out: Record<string, unknown> = {};\n for (const field of fields) {\n const raw = values[field.name];\n if (raw === undefined || raw === \"\") continue;\n out[field.name] = coerce(raw, field);\n }\n onSubmit(out);\n };\n\n return (\n <div className=\"max-w-2xl px-5 py-6 md:px-8\">\n <h1 className=\"mb-5 text-xl font-semibold tracking-tight\">{title}</h1>\n <Card>\n <form onSubmit={handleSubmit}>\n <CardHeader>\n <CardTitle className=\"text-base\">Details</CardTitle>\n {description ? <CardDescription>{description}</CardDescription> : null}\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {fields.map((field) => {\n const value = values[field.name] ?? \"\";\n const label = (\n <Label htmlFor={field.name} className=\"text-sm\">\n {humanizeLabel(field.name)}\n {field.required ? (\n <span className=\"ml-0.5 text-destructive\">*</span>\n ) : null}\n </Label>\n );\n if (field.kind === \"enum\" && field.enumValues) {\n return (\n <div key={field.name} className=\"space-y-1.5\">\n {label}\n <Select\n value={value}\n onValueChange={(v) => set(field.name, v)}\n >\n <SelectTrigger id={field.name}>\n <SelectValue placeholder={`Select ${field.name}`} />\n </SelectTrigger>\n <SelectContent>\n {field.enumValues.map((opt) => (\n <SelectItem key={String(opt)} value={String(opt)}>\n {String(opt)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n }\n if (field.kind === \"boolean\") {\n return (\n <div key={field.name} className=\"space-y-1.5\">\n {label}\n <Select\n value={value}\n onValueChange={(v) => set(field.name, v)}\n >\n <SelectTrigger id={field.name}>\n <SelectValue placeholder=\"Select\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"true\">true</SelectItem>\n <SelectItem value=\"false\">false</SelectItem>\n </SelectContent>\n </Select>\n </div>\n );\n }\n return (\n <div key={field.name} className=\"space-y-1.5\">\n {label}\n <Input\n id={field.name}\n type={inputType(field)}\n value={value}\n aria-invalid={errors[field.name] ? true : undefined}\n className={errors[field.name] ? \"border-destructive\" : undefined}\n placeholder={\n field.kind === \"array\" ? \"comma,separated,values\" : undefined\n }\n onChange={(e) => set(field.name, e.target.value)}\n />\n {errors[field.name] ? (\n <p className=\"text-xs text-destructive\">{errors[field.name]}</p>\n ) : null}\n </div>\n );\n })}\n {fields.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n This request takes no body fields — submit to send.\n </p>\n ) : null}\n </CardContent>\n <CardFooter className=\"flex items-center gap-3\">\n <Button type=\"submit\" disabled={submitting}>\n {submitting ? \"Saving…\" : \"Submit\"}\n </Button>\n {error ? (\n <span className=\"break-words text-sm text-destructive\">{error}</span>\n ) : null}\n {result ? (\n <span className=\"text-sm text-emerald-600\">{result}</span>\n ) : null}\n </CardFooter>\n </form>\n </Card>\n </div>\n );\n}\n",
|
|
18
|
+
"components/auto/ResourceIcon.tsx": "import {\n Activity,\n BarChart3,\n Bell,\n Box,\n Building2,\n Calendar,\n CreditCard,\n Database,\n FileText,\n Folder,\n Hash,\n Image,\n KeyRound,\n Layers,\n type LucideIcon,\n MapPin,\n MessageSquare,\n Package,\n Receipt,\n Settings,\n ShoppingCart,\n Tag,\n Ticket,\n Truck,\n Users,\n Wallet,\n Webhook,\n} from \"lucide-react\";\nimport * as React from \"react\";\n\n/**\n * Deterministic resource-name → icon mapping. Pure keyword match against the\n * resource name, no domain config — `orders` gets a cart, `payments` a card,\n * `users` people, and anything unmatched a neutral box. Gives every nav item,\n * stat card, and landing link a glyph without an LLM choosing one.\n */\nconst RULES: Array<readonly [RegExp, LucideIcon]> = [\n [/sale|order|cart|checkout|purchas/, ShoppingCart],\n [/product|commodit|item|catalog|good|inventor/, Package],\n [/user|customer|member|account|citizen|seller|admin|people|person|profile|contact/, Users],\n [/payment|deposit|transaction|invoice|bill|charge|refund|ledger/, CreditCard],\n [/wallet|balance|fund|mileage|point|reward|credit/, Wallet],\n [/deliver|shipment|shipping|fulfil|logistic/, Truck],\n [/folder|directory/, Folder],\n [/file|document|attachment/, FileText],\n [/image|photo|picture|media|gallery/, Image],\n [/channel/, Hash],\n [/section|categor|group|department|taxonom/, Layers],\n [/coupon|discount|promotion|voucher/, Ticket],\n [/tag|label|keyword/, Tag],\n [/message|comment|question|review|reply|chat|note|post/, MessageSquare],\n [/setting|config|system|preference/, Settings],\n [/auth|login|session|token|credential|security/, KeyRound],\n [/performance|metric|stat|analytic|report|dashboard|insight/, BarChart3],\n [/webhook|event|hook|trigger|subscription/, Webhook],\n [/notification|alert|reminder/, Bell],\n [/calendar|schedule|appointment|booking/, Calendar],\n [/location|address|place|region|geo|map/, MapPin],\n [/company|organization|store|shop|brand|tenant|workspace/, Building2],\n [/log|history|audit|activity|trace/, Activity],\n [/receipt|tax/, Receipt],\n [/data|record|entit/, Database],\n];\n\nfunction normalize(name: string): string {\n return name\n .replace(/^\\//, \"\")\n .replace(/[_/-]+/g, \" \")\n .toLowerCase();\n}\n\nexport function iconFor(name: string): LucideIcon {\n const norm = normalize(name);\n for (const [re, Icon] of RULES) if (re.test(norm)) return Icon;\n return Box;\n}\n\nexport function ResourceIcon({\n name,\n className,\n}: {\n name: string;\n className?: string;\n}) {\n const Icon = iconFor(name);\n return <Icon className={className} aria-hidden />;\n}\n",
|
|
19
|
+
"components/auto/ResourceLanding.tsx": "\"use client\";\n\nimport Link from \"next/link\";\nimport * as React from \"react\";\n\nimport { ResourceIcon } from \"@/components/auto/ResourceIcon\";\n\nexport interface LandingLink {\n href: string;\n title: string;\n description?: string;\n /**\n * Hub-ness — how many distinct child resources nest under this one\n * (`/files/{id}/metadata`, `/files/{id}/versions` → `files` scores 2+). A\n * genuine domain hub scores high; a leaf resource scores 0. Derived\n * deterministically from the producer→consumer graph; used to surface the key\n * resources above the long tail.\n */\n weight?: number;\n}\n\nexport interface ResourceLandingProps {\n title: string;\n subtitle?: string;\n links: LandingLink[];\n}\n\n/** Leading token of a resource path, singularized, for grouping. */\nfunction tokenOf(href: string): string {\n const resource = href.replace(/^\\//, \"\").split(\"/\")[0] ?? \"\";\n const first = resource.split(\"_\")[0] ?? resource;\n return first.length > 3 && first.endsWith(\"s\") ? first.slice(0, -1) : first;\n}\n\n/**\n * Group landing links by their leading resource token, mirroring the sidebar:\n * a token shared by >=2 resources becomes its own section, lone resources fall\n * under \"Resources\". Returns sections in a stable order (Resources first, then\n * named groups alphabetically).\n */\nfunction groupLinks(links: LandingLink[]): Array<[string, LandingLink[]]> {\n const count = new Map<string, number>();\n for (const l of links) count.set(tokenOf(l.href), (count.get(tokenOf(l.href)) ?? 0) + 1);\n const sections = new Map<string, LandingLink[]>();\n for (const l of links) {\n const token = tokenOf(l.href);\n const group =\n (count.get(token) ?? 0) >= 2\n ? token.charAt(0).toUpperCase() + token.slice(1)\n : \"Resources\";\n const bucket = sections.get(group) ?? [];\n bucket.push(l);\n sections.set(group, bucket);\n }\n return [...sections.entries()].sort((a, b) =>\n a[0] === \"Resources\" ? -1 : b[0] === \"Resources\" ? 1 : a[0].localeCompare(b[0]),\n );\n}\n\n/** A resource that parents >=2 distinct child resources is a domain hub. */\nconst KEY_WEIGHT = 2;\n\n/**\n * Order the landing into a hierarchy: the high-centrality \"Key resources\" lead\n * (the hubs other resources hang off of), then the rest grouped by prefix. This\n * is the IR's producer→consumer graph turned into visual priority — `files`,\n * `folders`, `users` rise above a deep niche resource deterministically, no LLM\n * guessing which resources \"matter\".\n */\nfunction buildSections(links: LandingLink[]): Array<[string, LandingLink[]]> {\n const key = links\n .filter((l) => (l.weight ?? 0) >= KEY_WEIGHT)\n .sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0) || a.title.localeCompare(b.title));\n const rest = links.filter((l) => (l.weight ?? 0) < KEY_WEIGHT);\n const sections: Array<[string, LandingLink[]]> = [];\n if (key.length > 0) sections.push([\"Key resources\", key]);\n sections.push(...groupLinks(rest));\n return sections;\n}\n\n/**\n * The grouped resource-link grid — sections mirroring the sidebar. Extracted so\n * both the plain landing and the dashboard (which adds a stat strip above) reuse\n * the exact same link layout instead of duplicating the grouping logic.\n */\nexport function ResourceLinks({ links }: { links: LandingLink[] }) {\n const sections = buildSections(links);\n if (links.length === 0) {\n return (\n <p className=\"text-sm text-muted-foreground\">\n No resources were found in this API document.\n </p>\n );\n }\n return (\n <div className=\"space-y-7\">\n {sections.map(([group, items]) => {\n const isKey = group === \"Key resources\";\n return (\n <section key={group}>\n <h2 className=\"mb-2.5 text-xs font-medium uppercase tracking-wider text-muted-foreground\">\n {group}\n <span className=\"ml-2 text-muted-foreground/60\">{items.length}</span>\n </h2>\n <div className=\"grid gap-2 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {items.map((link) => (\n <Link\n key={link.href}\n href={link.href}\n className={\n isKey\n ? \"group flex items-center justify-between rounded-lg border border-border bg-card px-4 py-3 text-sm shadow-sm transition-colors hover:border-primary/50 hover:bg-muted/40\"\n : \"group flex items-center justify-between rounded-md border border-border/70 px-3.5 py-2.5 text-sm transition-colors hover:border-primary/40 hover:bg-muted/40\"\n }\n >\n <span className=\"flex min-w-0 items-center gap-2.5\">\n <ResourceIcon\n name={link.href}\n className=\"h-4 w-4 shrink-0 text-muted-foreground\"\n />\n <span className=\"truncate font-medium\">{link.title}</span>\n </span>\n <span className=\"ml-2 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5\">\n →\n </span>\n </Link>\n ))}\n </div>\n </section>\n );\n })}\n </div>\n );\n}\n\n/**\n * Universal landing hub. A dense, grouped list of resources — sections mirror\n * the sidebar so a large API reads as a handful of areas, not a flat wall of\n * identical cards. No per-card description: the title already names the\n * resource, so repeating \"Browse and search X\" 36 times is pure noise.\n */\nexport function ResourceLanding(props: ResourceLandingProps) {\n const { title, subtitle, links } = props;\n return (\n <div className=\"px-5 py-8 md:px-8\">\n <div className=\"mb-7\">\n <h1 className=\"text-2xl font-semibold tracking-tight\">{title}</h1>\n {subtitle ? (\n <p className=\"mt-1.5 text-sm text-muted-foreground\">{subtitle}</p>\n ) : null}\n </div>\n <ResourceLinks links={links} />\n </div>\n );\n}\n",
|
|
20
|
+
"components/auto/ResourceTable.tsx": "\"use client\";\n\nimport Link from \"next/link\";\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/components/ui/table\";\n\nimport { humanizeLabel } from \"@/lib/utils\";\n\nimport { formatCell } from \"./formatValue\";\nimport type { ColumnSpec } from \"./types\";\n\nexport interface ResourceTableProps {\n title: string;\n description?: string;\n /** One column per field of the row type — the whole schema, nothing dropped. */\n columns: ColumnSpec[];\n /** Rows as returned by the SDK. Typed `unknown` so the page never casts. */\n rows: readonly unknown[];\n loading: boolean;\n error?: string | null;\n onRetry?: () => void;\n /** Build the detail-screen href for a row, or `null` when none exists. */\n rowHref?: (row: Record<string, unknown>) => string | null;\n emptyHint?: string;\n /** Search box, when the list endpoint exposes a text search param. */\n search?: { value: string; onChange: (value: string) => void };\n /** Pager, when the list endpoint exposes a page/offset param. `hasNext` is a\n * best-effort guess (a full page implies more) since totals are rarely typed. */\n pagination?: {\n page: number;\n onPrev: () => void;\n onNext: () => void;\n hasNext: boolean;\n };\n}\n\n/**\n * Universal list/table screen. Renders one column per `ColumnSpec` and one row\n * per SDK record. Schema-driven: the column set is the entire row type, so the\n * table is as dense and complete as the swagger allows — no field is ever\n * silently omitted. Handles loading / error / empty / data states.\n */\nexport function ResourceTable(props: ResourceTableProps) {\n const {\n title,\n description,\n columns,\n loading,\n error,\n onRetry,\n rowHref,\n emptyHint,\n search,\n pagination,\n } = props;\n // Defensive: never let a non-array `rows` (an optional collection field the\n // server omitted) crash the table on `.length` / `.map`.\n const rows: readonly unknown[] = Array.isArray(props.rows) ? props.rows : [];\n\n const body = (() => {\n if (error !== null && error !== undefined) {\n return (\n <Card className=\"border-destructive/40 bg-destructive/5\">\n <CardHeader>\n <CardTitle className=\"text-base text-destructive\">\n Couldn’t load {title.toLowerCase()}\n </CardTitle>\n <CardDescription className=\"break-words\">{error}</CardDescription>\n </CardHeader>\n {onRetry ? (\n <CardContent>\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry}>\n Retry\n </Button>\n </CardContent>\n ) : null}\n </Card>\n );\n }\n if (loading) {\n return (\n <div className=\"space-y-2\">\n {Array.from({ length: 8 }).map((_, i) => (\n <Skeleton key={i} className=\"h-10 w-full\" />\n ))}\n </div>\n );\n }\n if (rows.length === 0) {\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">No {title.toLowerCase()} yet</CardTitle>\n <CardDescription>\n {emptyHint ?? \"The list came back empty.\"}\n </CardDescription>\n </CardHeader>\n </Card>\n );\n }\n return (\n <div className=\"overflow-x-auto rounded-lg border bg-background shadow-sm\">\n <Table>\n <TableHeader className=\"sticky top-0 bg-muted/60 backdrop-blur\">\n <TableRow className=\"hover:bg-transparent\">\n {columns.map((c) => (\n <TableHead\n key={c.name}\n className=\"h-10 whitespace-nowrap text-xs font-medium uppercase tracking-wide text-muted-foreground\"\n >\n {humanizeLabel(c.name)}\n </TableHead>\n ))}\n {rowHref ? <TableHead className=\"w-0\" /> : null}\n </TableRow>\n </TableHeader>\n <TableBody>\n {rows.map((raw, i) => {\n const row = (raw ?? {}) as Record<string, unknown>;\n const href = rowHref ? rowHref(row) : null;\n return (\n <TableRow key={i} className=\"group\">\n {columns.map((c) => (\n <TableCell key={c.name} className=\"max-w-xs align-top text-sm\">\n {formatCell(row[c.name], c)}\n </TableCell>\n ))}\n {rowHref ? (\n <TableCell className=\"w-0 text-right\">\n {href ? (\n <Link\n href={href}\n className=\"inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-muted-foreground opacity-0 transition group-hover:opacity-100 hover:bg-muted hover:text-foreground\"\n >\n View →\n </Link>\n ) : null}\n </TableCell>\n ) : null}\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n );\n })();\n\n return (\n <div className=\"px-5 py-6 md:px-8\">\n <div className=\"mb-5 flex items-end justify-between gap-4\">\n <div>\n <h1 className=\"text-xl font-semibold tracking-tight\">{title}</h1>\n {description ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">{description}</p>\n ) : null}\n </div>\n <div className=\"flex items-center gap-3\">\n {search ? (\n <Input\n value={search.value}\n onChange={(e) => search.onChange(e.target.value)}\n placeholder=\"Search…\"\n className=\"h-9 w-48\"\n />\n ) : null}\n {!loading && !error ? (\n <span className=\"hidden text-xs text-muted-foreground sm:inline\">\n {rows.length} row{rows.length === 1 ? \"\" : \"s\"} · {columns.length} cols\n </span>\n ) : null}\n {onRetry ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry}>\n Refresh\n </Button>\n ) : null}\n </div>\n </div>\n {body}\n {pagination ? (\n <div className=\"mt-4 flex items-center justify-end gap-2\">\n <span className=\"mr-1 text-xs text-muted-foreground\">\n Page {pagination.page}\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={pagination.page <= 1}\n onClick={pagination.onPrev}\n >\n ← Prev\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={!pagination.hasNext}\n onClick={pagination.onNext}\n >\n Next →\n </Button>\n </div>\n ) : null}\n </div>\n );\n}\n",
|
|
21
|
+
"components/auto/formatValue.tsx": "import Link from \"next/link\";\nimport * as React from \"react\";\n\nimport { Badge } from \"@/components/ui/badge\";\n\nimport type { ColumnSpec } from \"./types\";\n\n/** The id of a nested record, when it has a usable string/number `id`. */\nfunction recordId(value: unknown): string | null {\n if (value === null || typeof value !== \"object\") return null;\n const id = (value as Record<string, unknown>).id;\n return typeof id === \"string\" || typeof id === \"number\" ? String(id) : null;\n}\n\n/**\n * Field names, in priority order, used to pick a human-readable label for a\n * nested object / reference value (a category, a tag, an author …). The SDK\n * returns the whole nested object; rather than dumping JSON we surface its most\n * identifying string.\n */\nconst IDENTITY_KEYS = [\n \"name\",\n \"title\",\n \"nickname\",\n \"label\",\n \"code\",\n \"email\",\n \"id\",\n];\n\n/** Best-effort human label for a nested object value. Never dumps JSON. */\nexport function identify(value: unknown): string {\n if (value === null || value === undefined) return \"—\";\n if (typeof value !== \"object\") return String(value);\n const obj = value as Record<string, unknown>;\n for (const key of IDENTITY_KEYS) {\n const v = obj[key];\n if (typeof v === \"string\" && v.length > 0) return v;\n if (typeof v === \"number\") return String(v);\n }\n return \"{…}\";\n}\n\n/** Format an ISO date string for display, falling back to the raw text. */\nexport function formatDate(value: unknown): string {\n if (typeof value !== \"string\") return \"—\";\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleString();\n}\n\n/** Status keywords → badge variant. Lets enums read as state, not gray noise. */\nconst STATUS_VARIANT: Record<string, \"success\" | \"warning\" | \"destructive\"> = {\n active: \"success\",\n enabled: \"success\",\n approved: \"success\",\n completed: \"success\",\n complete: \"success\",\n done: \"success\",\n paid: \"success\",\n success: \"success\",\n succeeded: \"success\",\n open: \"success\",\n published: \"success\",\n online: \"success\",\n pending: \"warning\",\n processing: \"warning\",\n waiting: \"warning\",\n review: \"warning\",\n draft: \"warning\",\n paused: \"warning\",\n failed: \"destructive\",\n error: \"destructive\",\n rejected: \"destructive\",\n canceled: \"destructive\",\n cancelled: \"destructive\",\n disabled: \"destructive\",\n inactive: \"destructive\",\n expired: \"destructive\",\n banned: \"destructive\",\n deleted: \"destructive\",\n closed: \"destructive\",\n offline: \"destructive\",\n};\n\n/** Pick a badge variant for an enum value by its semantic state, else neutral. */\nfunction enumVariant(\n value: string,\n): \"success\" | \"warning\" | \"destructive\" | \"secondary\" {\n return STATUS_VARIANT[value.toLowerCase().trim()] ?? \"secondary\";\n}\n\nconst IMAGE_EXT = /\\.(png|jpe?g|gif|webp|avif|svg)(\\?|#|$)/i;\n\n/** True when a string value looks like a displayable image URL. */\nfunction looksLikeImage(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n /^(https?:|\\/|data:image\\/)/.test(value) &&\n (IMAGE_EXT.test(value) || value.startsWith(\"data:image/\"))\n );\n}\n\n/**\n * Render one cell value according to its field spec. The single place that\n * decides how each `FieldKind` looks — enums become badges, booleans a\n * check/cross, arrays a count, nested objects their identifying label, dates a\n * locale string, and long strings truncate. Shared by the table and the detail\n * view so both stay visually consistent.\n */\nexport function formatCell(value: unknown, col: ColumnSpec): React.ReactNode {\n if (value === null || value === undefined) {\n return <span className=\"text-muted-foreground\">—</span>;\n }\n switch (col.kind) {\n case \"boolean\":\n return value ? (\n <span className=\"font-medium text-success\">✓</span>\n ) : (\n <span className=\"text-muted-foreground\">✗</span>\n );\n case \"enum\":\n return (\n <Badge variant={enumVariant(String(value))} className=\"font-normal\">\n {String(value)}\n </Badge>\n );\n case \"array\": {\n if (!Array.isArray(value)) return <span className=\"text-muted-foreground\">—</span>;\n if (value.length === 0) return <span className=\"text-muted-foreground\">empty</span>;\n // Array of primitives → show inline; array of objects → count.\n const allPrimitive = value.every((v) => typeof v !== \"object\" || v === null);\n if (allPrimitive) {\n return (\n <span className=\"block max-w-xs truncate\">\n {value.map((v) => String(v)).join(\", \")}\n </span>\n );\n }\n return (\n <span className=\"text-muted-foreground\">\n {value.length} item{value.length === 1 ? \"\" : \"s\"}\n </span>\n );\n }\n case \"ref\":\n case \"object\": {\n const label = identify(value);\n const id = recordId(value);\n if (col.hrefBase !== undefined && id !== null) {\n return (\n <Link\n href={`${col.hrefBase}/${id}`}\n className=\"block max-w-xs truncate text-primary hover:underline\"\n >\n {label}\n </Link>\n );\n }\n return <span className=\"block max-w-xs truncate\">{label}</span>;\n }\n case \"number\":\n return <span className=\"tabular-nums\">{String(value)}</span>;\n case \"string\":\n if (col.format === \"date-time\" || col.format === \"date\") {\n return <span className=\"whitespace-nowrap\">{formatDate(value)}</span>;\n }\n if (looksLikeImage(value)) {\n return (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={value}\n alt=\"\"\n loading=\"lazy\"\n className=\"h-9 w-9 rounded-md border border-border object-cover\"\n />\n );\n }\n return <span className=\"block max-w-xs truncate\">{String(value)}</span>;\n default:\n return (\n <span className=\"block max-w-xs truncate\">\n {typeof value === \"object\" ? identify(value) : String(value)}\n </span>\n );\n }\n}\n",
|
|
22
|
+
"components/auto/types.ts": "/**\n * Field metadata the deterministic page generator bakes into every screen.\n *\n * Mirrors `IFieldSpec` from the generator (`src/utils/extractFields.ts`) — the\n * generator runs in Node and walks the OpenAPI schema, then serializes the\n * resulting field list as a literal into each `page.tsx`. The universal\n * `Resource*` components below read these specs to render columns / detail rows\n * / form inputs. One field of the row/response/request type → one column / row\n * / input, so \"dense, complete tables\" is a loop, not an LLM guess.\n */\nexport type FieldKind =\n | \"string\"\n | \"number\"\n | \"boolean\"\n | \"enum\"\n | \"array\"\n | \"object\"\n | \"ref\"\n | \"union\"\n | \"unknown\";\n\n/** A table column / detail row. */\nexport interface ColumnSpec {\n name: string;\n kind: FieldKind;\n /** String format (`uuid`, `date-time`, `uri`, …) when present. */\n format?: string;\n /** Component type name for a `ref`, or the element type of an `array`. */\n ref?: string;\n /** For an `array`, the element's kind. */\n itemKind?: FieldKind;\n /** Allowed values for an `enum`. */\n enumValues?: Array<string | number | boolean>;\n /** When a `ref` field's type resolves to a browsable resource, the list route\n * to link to (`/sales`). The cell links to `${hrefBase}/${value.id}`. */\n hrefBase?: string;\n}\n\n/** A form input — a column plus whether the request schema requires it. */\nexport interface FieldInput extends ColumnSpec {\n required: boolean;\n}\n",
|
|
23
|
+
"components/ui/badge.tsx": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst badgeVariants = cva(\n \"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\",\n {\n variants: {\n variant: {\n default:\n \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n secondary:\n \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive:\n \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n success:\n \"border-transparent bg-success text-success-foreground hover:bg-success/80\",\n warning:\n \"border-transparent bg-warning text-warning-foreground hover:bg-warning/80\",\n outline: \"text-foreground\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n },\n);\n\nexport interface BadgeProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof badgeVariants> {}\n\nfunction Badge({ className, variant, ...props }: BadgeProps) {\n return (\n <div className={cn(badgeVariants({ variant }), className)} {...props} />\n );\n}\n\nexport { Badge, badgeVariants };\n",
|
|
24
|
+
"components/ui/button.tsx": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n // SpaceX CTA voice: uppercase, wide tracking, semibold — \"ORDER NOW\".\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-semibold uppercase tracking-wider ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n outline:\n \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-9 rounded-md px-3\",\n lg: \"h-11 rounded-md px-8\",\n icon: \"h-10 w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n {...props}\n />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n",
|
|
25
|
+
"components/ui/card.tsx": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Card = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"rounded-lg border bg-card text-card-foreground shadow-sm\",\n className,\n )}\n {...props}\n />\n));\nCard.displayName = \"Card\";\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex flex-col space-y-1.5 p-6\", className)}\n {...props}\n />\n));\nCardHeader.displayName = \"CardHeader\";\n\nconst CardTitle = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"text-2xl font-semibold leading-none tracking-tight\",\n className,\n )}\n {...props}\n />\n));\nCardTitle.displayName = \"CardTitle\";\n\nconst CardDescription = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n));\nCardDescription.displayName = \"CardDescription\";\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\n));\nCardContent.displayName = \"CardContent\";\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex items-center p-6 pt-0\", className)}\n {...props}\n />\n));\nCardFooter.displayName = \"CardFooter\";\n\nexport {\n Card,\n CardHeader,\n CardFooter,\n CardTitle,\n CardDescription,\n CardContent,\n};\n",
|
|
26
|
+
"components/ui/dialog.tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Dialog = DialogPrimitive.Root;\nconst DialogTrigger = DialogPrimitive.Trigger;\nconst DialogPortal = DialogPrimitive.Portal;\nconst DialogClose = DialogPrimitive.Close;\n\nconst DialogOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(\n \"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n className,\n )}\n {...props}\n />\n));\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;\n\nconst DialogContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Content\n ref={ref}\n className={cn(\n \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg\",\n className,\n )}\n {...props}\n >\n {children}\n <DialogPrimitive.Close className=\"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground\">\n <X className=\"h-4 w-4\" />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n </DialogPrimitive.Content>\n </DialogPortal>\n));\nDialogContent.displayName = DialogPrimitive.Content.displayName;\n\nconst DialogHeader = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n <div\n className={cn(\n \"flex flex-col space-y-1.5 text-center sm:text-left\",\n className,\n )}\n {...props}\n />\n);\nDialogHeader.displayName = \"DialogHeader\";\n\nconst DialogFooter = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n <div\n className={cn(\n \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n className,\n )}\n {...props}\n />\n);\nDialogFooter.displayName = \"DialogFooter\";\n\nconst DialogTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\n \"text-lg font-semibold leading-none tracking-tight\",\n className,\n )}\n {...props}\n />\n));\nDialogTitle.displayName = DialogPrimitive.Title.displayName;\n\nconst DialogDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n));\nDialogDescription.displayName = DialogPrimitive.Description.displayName;\n\nexport {\n Dialog,\n DialogPortal,\n DialogOverlay,\n DialogTrigger,\n DialogClose,\n DialogContent,\n DialogHeader,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n};\n",
|
|
27
|
+
"components/ui/input.tsx": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Input = React.forwardRef<\n HTMLInputElement,\n React.InputHTMLAttributes<HTMLInputElement>\n>(({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n ref={ref}\n {...props}\n />\n );\n});\nInput.displayName = \"Input\";\n\nexport { Input };\n",
|
|
28
|
+
"components/ui/label.tsx": "import * as React from \"react\";\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst labelVariants = cva(\n \"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\",\n);\n\nconst Label = React.forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &\n VariantProps<typeof labelVariants>\n>(({ className, ...props }, ref) => (\n <LabelPrimitive.Root\n ref={ref}\n className={cn(labelVariants(), className)}\n {...props}\n />\n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n\nexport { Label };\n",
|
|
29
|
+
"components/ui/pagination.tsx": "import * as React from \"react\";\nimport { ChevronLeft, ChevronRight, MoreHorizontal } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { ButtonProps, buttonVariants } from \"@/components/ui/button\";\n\nconst Pagination = ({ className, ...props }: React.ComponentProps<\"nav\">) => (\n <nav\n role=\"navigation\"\n aria-label=\"pagination\"\n className={cn(\"mx-auto flex w-full justify-center\", className)}\n {...props}\n />\n);\nPagination.displayName = \"Pagination\";\n\nconst PaginationContent = React.forwardRef<\n HTMLUListElement,\n React.ComponentProps<\"ul\">\n>(({ className, ...props }, ref) => (\n <ul\n ref={ref}\n className={cn(\"flex flex-row items-center gap-1\", className)}\n {...props}\n />\n));\nPaginationContent.displayName = \"PaginationContent\";\n\nconst PaginationItem = React.forwardRef<\n HTMLLIElement,\n React.ComponentProps<\"li\">\n>(({ className, ...props }, ref) => (\n <li ref={ref} className={cn(\"\", className)} {...props} />\n));\nPaginationItem.displayName = \"PaginationItem\";\n\ntype PaginationLinkProps = {\n isActive?: boolean;\n} & Pick<ButtonProps, \"size\"> &\n React.ComponentProps<\"a\">;\n\nconst PaginationLink = ({\n className,\n isActive,\n size = \"icon\",\n ...props\n}: PaginationLinkProps) => (\n <a\n aria-current={isActive ? \"page\" : undefined}\n className={cn(\n buttonVariants({\n variant: isActive ? \"outline\" : \"ghost\",\n size,\n }),\n className,\n )}\n {...props}\n />\n);\nPaginationLink.displayName = \"PaginationLink\";\n\nconst PaginationPrevious = ({\n className,\n ...props\n}: React.ComponentProps<typeof PaginationLink>) => (\n <PaginationLink\n aria-label=\"Go to previous page\"\n size=\"default\"\n className={cn(\"gap-1 pl-2.5\", className)}\n {...props}\n >\n <ChevronLeft className=\"h-4 w-4\" />\n <span>Previous</span>\n </PaginationLink>\n);\nPaginationPrevious.displayName = \"PaginationPrevious\";\n\nconst PaginationNext = ({\n className,\n ...props\n}: React.ComponentProps<typeof PaginationLink>) => (\n <PaginationLink\n aria-label=\"Go to next page\"\n size=\"default\"\n className={cn(\"gap-1 pr-2.5\", className)}\n {...props}\n >\n <span>Next</span>\n <ChevronRight className=\"h-4 w-4\" />\n </PaginationLink>\n);\nPaginationNext.displayName = \"PaginationNext\";\n\nconst PaginationEllipsis = ({\n className,\n ...props\n}: React.ComponentProps<\"span\">) => (\n <span\n aria-hidden\n className={cn(\"flex h-9 w-9 items-center justify-center\", className)}\n {...props}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n <span className=\"sr-only\">More pages</span>\n </span>\n);\nPaginationEllipsis.displayName = \"PaginationEllipsis\";\n\nexport {\n Pagination,\n PaginationContent,\n PaginationLink,\n PaginationItem,\n PaginationPrevious,\n PaginationNext,\n PaginationEllipsis,\n};\n",
|
|
30
|
+
"components/ui/select.tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { Check, ChevronDown, ChevronUp } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Select = SelectPrimitive.Root;\nconst SelectGroup = SelectPrimitive.Group;\nconst SelectValue = SelectPrimitive.Value;\n\nconst SelectTrigger = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n <SelectPrimitive.Trigger\n ref={ref}\n className={cn(\n \"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1\",\n className,\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n <ChevronDown className=\"h-4 w-4 opacity-50\" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n));\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName;\n\nconst SelectContent = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = \"popper\", ...props }, ref) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n ref={ref}\n className={cn(\n \"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n position === \"popper\" &&\n \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n className,\n )}\n position={position}\n {...props}\n >\n <SelectPrimitive.Viewport\n className={cn(\n \"p-1\",\n position === \"popper\" &&\n \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\",\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n));\nSelectContent.displayName = SelectPrimitive.Content.displayName;\n\nconst SelectItem = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n <SelectPrimitive.Item\n ref={ref}\n className={cn(\n \"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n )}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <Check className=\"h-4 w-4\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n));\nSelectItem.displayName = SelectPrimitive.Item.displayName;\n\nexport {\n Select,\n SelectGroup,\n SelectValue,\n SelectTrigger,\n SelectContent,\n SelectItem,\n};\n",
|
|
31
|
+
"components/ui/sheet.tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Sheet = SheetPrimitive.Root;\nconst SheetTrigger = SheetPrimitive.Trigger;\nconst SheetClose = SheetPrimitive.Close;\nconst SheetPortal = SheetPrimitive.Portal;\n\nconst SheetOverlay = React.forwardRef<\n React.ElementRef<typeof SheetPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <SheetPrimitive.Overlay\n className={cn(\n \"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n className,\n )}\n {...props}\n ref={ref}\n />\n));\nSheetOverlay.displayName = SheetPrimitive.Overlay.displayName;\n\nconst sheetVariants = cva(\n \"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out\",\n {\n variants: {\n side: {\n top: \"inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n bottom:\n \"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n left: \"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm\",\n right:\n \"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm\",\n },\n },\n defaultVariants: {\n side: \"right\",\n },\n },\n);\n\ninterface SheetContentProps\n extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,\n VariantProps<typeof sheetVariants> {}\n\nconst SheetContent = React.forwardRef<\n React.ElementRef<typeof SheetPrimitive.Content>,\n SheetContentProps\n>(({ side = \"right\", className, children, ...props }, ref) => (\n <SheetPortal>\n <SheetOverlay />\n <SheetPrimitive.Content\n ref={ref}\n className={cn(sheetVariants({ side }), className)}\n {...props}\n >\n {children}\n <SheetPrimitive.Close className=\"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary\">\n <X className=\"h-4 w-4\" />\n <span className=\"sr-only\">Close</span>\n </SheetPrimitive.Close>\n </SheetPrimitive.Content>\n </SheetPortal>\n));\nSheetContent.displayName = SheetPrimitive.Content.displayName;\n\nconst SheetHeader = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n <div\n className={cn(\n \"flex flex-col space-y-2 text-center sm:text-left\",\n className,\n )}\n {...props}\n />\n);\nSheetHeader.displayName = \"SheetHeader\";\n\nconst SheetFooter = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n <div\n className={cn(\n \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n className,\n )}\n {...props}\n />\n);\nSheetFooter.displayName = \"SheetFooter\";\n\nconst SheetTitle = React.forwardRef<\n React.ElementRef<typeof SheetPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <SheetPrimitive.Title\n ref={ref}\n className={cn(\"text-lg font-semibold text-foreground\", className)}\n {...props}\n />\n));\nSheetTitle.displayName = SheetPrimitive.Title.displayName;\n\nconst SheetDescription = React.forwardRef<\n React.ElementRef<typeof SheetPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <SheetPrimitive.Description\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n));\nSheetDescription.displayName = SheetPrimitive.Description.displayName;\n\nexport {\n Sheet,\n SheetTrigger,\n SheetClose,\n SheetContent,\n SheetHeader,\n SheetFooter,\n SheetTitle,\n SheetDescription,\n};\n",
|
|
32
|
+
"components/ui/skeleton.tsx": "import { cn } from \"@/lib/utils\";\n\nfunction Skeleton({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn(\"animate-pulse rounded-md bg-muted\", className)}\n {...props}\n />\n );\n}\n\nexport { Skeleton };\n",
|
|
33
|
+
"components/ui/table.tsx": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Table = React.forwardRef<\n HTMLTableElement,\n React.HTMLAttributes<HTMLTableElement>\n>(({ className, ...props }, ref) => (\n <div className=\"relative w-full overflow-auto\">\n <table\n ref={ref}\n className={cn(\"w-full caption-bottom text-sm\", className)}\n {...props}\n />\n </div>\n));\nTable.displayName = \"Table\";\n\nconst TableHeader = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <thead ref={ref} className={cn(\"[&_tr]:border-b\", className)} {...props} />\n));\nTableHeader.displayName = \"TableHeader\";\n\nconst TableBody = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tbody\n ref={ref}\n className={cn(\"[&_tr:last-child]:border-0\", className)}\n {...props}\n />\n));\nTableBody.displayName = \"TableBody\";\n\nconst TableFooter = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tfoot\n ref={ref}\n className={cn(\n \"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0\",\n className,\n )}\n {...props}\n />\n));\nTableFooter.displayName = \"TableFooter\";\n\nconst TableRow = React.forwardRef<\n HTMLTableRowElement,\n React.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n <tr\n ref={ref}\n className={cn(\n \"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted\",\n className,\n )}\n {...props}\n />\n));\nTableRow.displayName = \"TableRow\";\n\nconst TableHead = React.forwardRef<\n HTMLTableCellElement,\n React.ThHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n <th\n ref={ref}\n className={cn(\n \"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n className,\n )}\n {...props}\n />\n));\nTableHead.displayName = \"TableHead\";\n\nconst TableCell = React.forwardRef<\n HTMLTableCellElement,\n React.TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n <td\n ref={ref}\n className={cn(\n \"p-4 align-middle [&:has([role=checkbox])]:pr-0\",\n className,\n )}\n {...props}\n />\n));\nTableCell.displayName = \"TableCell\";\n\nconst TableCaption = React.forwardRef<\n HTMLTableCaptionElement,\n React.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n <caption\n ref={ref}\n className={cn(\"mt-4 text-sm text-muted-foreground\", className)}\n {...props}\n />\n));\nTableCaption.displayName = \"TableCaption\";\n\nexport {\n Table,\n TableHeader,\n TableBody,\n TableFooter,\n TableHead,\n TableRow,\n TableCell,\n TableCaption,\n};\n",
|
|
34
|
+
"components/ui/tabs.tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Tabs = TabsPrimitive.Root;\n\nconst TabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.List\n ref={ref}\n className={cn(\n \"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground\",\n className,\n )}\n {...props}\n />\n));\nTabsList.displayName = TabsPrimitive.List.displayName;\n\nconst TabsTrigger = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm\",\n className,\n )}\n {...props}\n />\n));\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName;\n\nconst TabsContent = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Content\n ref={ref}\n className={cn(\n \"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n className,\n )}\n {...props}\n />\n));\nTabsContent.displayName = TabsPrimitive.Content.displayName;\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent };\n",
|
|
35
|
+
"lib/utils.ts": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Canonical shadcn/ui class-name helper. Merges the provided class strings\n * via `clsx` (truthy / object / array shapes) and resolves Tailwind\n * conflicts via `twMerge` so the last winning utility on each axis stays.\n *\n * Every component under `components/ui/*.tsx` imports this as `cn`.\n * Without this file every generated frontend ships with a broken\n * `Cannot find module '@/lib/utils'` resolution and `npm run build`\n * fails — the typecheck phase reports it as 13 separate errors (one per\n * component file).\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n\n/**\n * Humanize a schema field / column name for display: `download_url` →\n * `Download url`, `nameConflicts` → `Name conflicts`, `id` → `Id`. Splits\n * snake_case and camelCase, collapses whitespace, capitalizes the first letter.\n * Used for table headers and detail field labels so a raw API field name never\n * shows through in the UI.\n */\nexport function humanizeLabel(name: string): string {\n const spaced = name\n .replace(/[_-]+/g, \" \")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/\\s+/g, \" \")\n .trim();\n return spaced.length === 0\n ? spaced\n : spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n",
|
|
36
|
+
"next.config.mjs": "// The generated SDK uses `typia.random()` / `typia.is()` for the\n// `simulate: true` code path. Both calls require typia's compile-time\n// transform — without it, the SDK throws \"no transform has been\n// configured\" at the first network call and every page renders an error\n// state. Wrap the Next.js config with `@typia/unplugin/next` so the\n// transform runs during both `next dev` and `next build`.\nimport unTypiaNext from \"@typia/unplugin/next\";\n\n// The backend the frontend talks to. Falls back to the bundled\n// `scripts/start-shopping-backend.sh` default (NestJS on port 37001)\n// when no override is supplied.\nconst BACKEND_ORIGIN =\n process.env.NEXT_PUBLIC_API_HOST?.trim() || \"http://127.0.0.1:37001\";\n\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n // `output: \"standalone\"` is required by the Sandbox Dockerfile. Do not\n // remove — the Docker `HEALTHCHECK` + production CMD both depend on\n // `.next/standalone/server.js` existing.\n output: \"standalone\",\n reactStrictMode: true,\n // The SDK's `simulate: true` path returns mock data whose URL fields\n // are generated by `typia.random()` and point at arbitrary hostnames.\n // The default Next.js image optimizer rejects unknown hostnames, so\n // any page that renders `<Image src={sdkUrl} />` either fails to load\n // or 502s in the dev overlay. Allow every https hostname for images\n // since the bundled deploy is the in-house Sandbox and the operator\n // chooses what to wire.\n images: {\n remotePatterns: [\n { protocol: \"https\", hostname: \"**\" },\n { protocol: \"http\", hostname: \"**\" },\n ],\n },\n // Proxy backend calls through the Next dev/runtime server so the\n // browser sees them as same-origin. Avoids the classic CORS dead-end\n // where the upstream backend's `Access-Control-Allow-Methods` lists\n // only `GET,HEAD,POST` and every PATCH / PUT / DELETE preflight is\n // rejected — exactly how the samchon shopping backend ships out of\n // the box. The connection module points the SDK at `/_backend/*` and\n // this rewrite forwards it to the real upstream.\n async rewrites() {\n return [\n {\n source: \"/_backend/:path*\",\n destination: `${BACKEND_ORIGIN}/:path*`,\n },\n ];\n },\n};\n\nexport default unTypiaNext(nextConfig);\n",
|
|
37
|
+
"package.json": "{\n \"name\": \"autobe-frontend\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"next dev\",\n \"build\": \"next build\",\n \"start\": \"next start\",\n \"lint\": \"next lint\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"dependencies\": {\n \"@nestia/fetcher\": \"^11.0.1\",\n \"@radix-ui/react-dialog\": \"^1.1.2\",\n \"@radix-ui/react-dropdown-menu\": \"^2.1.2\",\n \"@radix-ui/react-label\": \"^2.1.0\",\n \"@radix-ui/react-select\": \"^2.1.2\",\n \"@radix-ui/react-slot\": \"^1.1.0\",\n \"@radix-ui/react-tabs\": \"^1.1.1\",\n \"@radix-ui/react-toast\": \"^1.2.2\",\n \"@tanstack/react-query\": \"^5.59.16\",\n \"class-variance-authority\": \"^0.7.0\",\n \"clsx\": \"^2.1.1\",\n \"lucide-react\": \"^0.453.0\",\n \"next\": \"14.2.18\",\n \"react\": \"18.3.1\",\n \"react-dom\": \"18.3.1\",\n \"react-hook-form\": \"^7.53.1\",\n \"tailwind-merge\": \"^2.5.4\",\n \"tailwindcss-animate\": \"^1.0.7\",\n \"typia\": \"^12.0.2\",\n \"zod\": \"^3.23.8\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.16.13\",\n \"@types/react\": \"18.3.12\",\n \"@types/react-dom\": \"18.3.1\",\n \"@typia/unplugin\": \"^12.0.2\",\n \"autoprefixer\": \"^10.4.20\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-next\": \"14.2.18\",\n \"postcss\": \"^8.4.49\",\n \"tailwindcss\": \"^3.4.15\",\n \"typescript\": \"^5.6.3\"\n }\n}\n",
|
|
38
|
+
"postcss.config.js": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n",
|
|
39
|
+
"scripts/start-shopping-backend.sh": "#!/usr/bin/env bash\n# Boot the @samchon/shopping reference backend locally so the AutoView\n# frontend can render real product data (Apple Watch / MacBook / etc.)\n# instead of typia-random gibberish or hitting the dead public host.\n#\n# What this does:\n# 1. Clone (or refresh) the samchon/shopping monorepo into `.shopping-backend/`.\n# 2. Run `pnpm install` once — that creates the SQLite DB and schema.\n# 3. Run `pnpm start` — that seeds demo data on first boot and listens on\n# http://127.0.0.1:37001.\n#\n# Requirements: git, node 20+, pnpm (or corepack). No Docker, no PostgreSQL.\n#\n# Tip: leave this script running in its own terminal. The frontend's\n# `npm run dev` in another terminal will then hit a live backend.\n#\n# When the backend is up, the generated frontend already defaults to\n# `http://localhost:37001` with `simulate: false`, so the catalog page\n# will render real shopping data without any extra environment knobs.\n\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_ROOT=\"$(cd \"${SCRIPT_DIR}/..\" && pwd)\"\nBACKEND_DIR=\"${PROJECT_ROOT}/.shopping-backend\"\nREPO_URL=\"https://github.com/samchon/shopping.git\"\n\ncommand -v git >/dev/null 2>&1 || {\n echo \"error: git is required but was not found in PATH\" >&2\n exit 1\n}\n\nif command -v pnpm >/dev/null 2>&1; then\n PNPM=(pnpm)\nelif command -v corepack >/dev/null 2>&1; then\n PNPM=(corepack pnpm)\nelse\n echo \"error: neither pnpm nor corepack is installed — install Node 20+ then run \\`corepack enable\\`\" >&2\n exit 1\nfi\n\nif [ ! -d \"${BACKEND_DIR}/.git\" ]; then\n echo \"==> cloning samchon/shopping into ${BACKEND_DIR}\"\n git clone --depth 1 \"${REPO_URL}\" \"${BACKEND_DIR}\"\nelse\n echo \"==> updating samchon/shopping in ${BACKEND_DIR}\"\n git -C \"${BACKEND_DIR}\" pull --ff-only --quiet || true\nfi\n\ncd \"${BACKEND_DIR}\"\n\necho \"==> pnpm install (creates SQLite schema on first run)\"\n\"${PNPM[@]}\" install\n\necho \"==> pnpm start (listens on http://127.0.0.1:37001, seeds demo data on first boot)\"\nexec \"${PNPM[@]}\" --filter @samchon/shopping-backend start\n",
|
|
40
|
+
"tailwind.config.ts": "import type { Config } from \"tailwindcss\";\n\nconst config: Config = {\n darkMode: [\"class\"],\n content: [\n \"./app/**/*.{ts,tsx}\",\n \"./components/**/*.{ts,tsx}\",\n \"./lib/**/*.{ts,tsx}\",\n ],\n theme: {\n container: {\n center: true,\n padding: \"2rem\",\n screens: {\n \"2xl\": \"1400px\",\n },\n },\n extend: {\n fontFamily: {\n // Inner fallback inside var() so a missing `--font-sans` (font failed to\n // load / offline build) resolves to a sans stack, never the UA serif.\n sans: [\n \"var(--font-sans, ui-sans-serif)\",\n \"ui-sans-serif\",\n \"system-ui\",\n \"sans-serif\",\n ],\n },\n colors: {\n border: \"hsl(var(--border))\",\n input: \"hsl(var(--input))\",\n ring: \"hsl(var(--ring))\",\n background: \"hsl(var(--background))\",\n foreground: \"hsl(var(--foreground))\",\n primary: {\n DEFAULT: \"hsl(var(--primary))\",\n foreground: \"hsl(var(--primary-foreground))\",\n },\n secondary: {\n DEFAULT: \"hsl(var(--secondary))\",\n foreground: \"hsl(var(--secondary-foreground))\",\n },\n success: {\n DEFAULT: \"hsl(var(--success))\",\n foreground: \"hsl(var(--success-foreground))\",\n },\n warning: {\n DEFAULT: \"hsl(var(--warning))\",\n foreground: \"hsl(var(--warning-foreground))\",\n },\n destructive: {\n DEFAULT: \"hsl(var(--destructive))\",\n foreground: \"hsl(var(--destructive-foreground))\",\n },\n muted: {\n DEFAULT: \"hsl(var(--muted))\",\n foreground: \"hsl(var(--muted-foreground))\",\n },\n accent: {\n DEFAULT: \"hsl(var(--accent))\",\n foreground: \"hsl(var(--accent-foreground))\",\n },\n popover: {\n DEFAULT: \"hsl(var(--popover))\",\n foreground: \"hsl(var(--popover-foreground))\",\n },\n card: {\n DEFAULT: \"hsl(var(--card))\",\n foreground: \"hsl(var(--card-foreground))\",\n },\n },\n borderRadius: {\n lg: \"var(--radius)\",\n md: \"calc(var(--radius) - 2px)\",\n sm: \"calc(var(--radius) - 4px)\",\n },\n keyframes: {\n \"accordion-down\": {\n from: { height: \"0\" },\n to: { height: \"var(--radix-accordion-content-height)\" },\n },\n \"accordion-up\": {\n from: { height: \"var(--radix-accordion-content-height)\" },\n to: { height: \"0\" },\n },\n },\n animation: {\n \"accordion-down\": \"accordion-down 0.2s ease-out\",\n \"accordion-up\": \"accordion-up 0.2s ease-out\",\n },\n },\n },\n plugins: [require(\"tailwindcss-animate\")],\n};\n\nexport default config;\n",
|
|
41
|
+
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": false,\n \"skipLibCheck\": true,\n \"strict\": true,\n \"noEmit\": true,\n \"esModuleInterop\": true,\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n \"resolveJsonModule\": true,\n \"isolatedModules\": true,\n \"jsx\": \"preserve\",\n \"incremental\": true,\n \"plugins\": [{ \"name\": \"next\" }],\n \"baseUrl\": \".\",\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\n \"next-env.d.ts\",\n \"**/*.ts\",\n \"**/*.tsx\",\n \".next/types/**/*.ts\"\n ],\n \"exclude\": [\"node_modules\"]\n}\n"
|
|
42
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/* eslint-disable no-template-curly-in-string */
|
|
2
|
+
export const enum AutoViewSystemPromptConstant {
|
|
3
|
+
AUTOVIEW_RENDER = "<!--\nfilename: AUTOVIEW_RENDER.md\n-->\nYou are the Render phase of the AutoView agent.\n\nYou receive one screen from the product plan and produce a complete,\nrunnable Next.js page (TSX) that composes the SDK endpoints listed for\nthat screen into a single coherent UI. You are not redesigning the\nproduct, choosing the actor, or inventing endpoints — those decisions\nwere already made by the Product Plan phase. You translate the plan\ninto TSX.\n\n## Stack\n\n- Next.js 15 App Router. Output a single TSX file with `export default\n function Page()`.\n- React 18 client component by default. Start the file with\n `\"use client\";` because every page calls the SDK from the browser.\n- shadcn/ui primitives, already vendored under `@/components/ui/*`.\n Common primitives available: `Button`, `Card` (with `CardHeader`,\n `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`),\n `Input`, `Label`, `Table` (`TableHeader`, `TableBody`, `TableHead`,\n `TableRow`, `TableCell`, `TableCaption`), `Dialog`, `Sheet`,\n `Tabs`, `Select`, `Pagination`, `Badge`, `Skeleton`. Compose them.\n- Tailwind utilities for layout and spacing. The CSS variables behind\n shadcn (`--background`, `--foreground`, `--primary`, …) are\n configured in `globals.css`. Stick to the design tokens unless the\n design theme asks for otherwise.\n- TypeScript strict. No `any` cast that isn't justified by the SDK\n surface. Use the SDK's exported types directly.\n\n## SDK access\n\nThe bundled SDK adapter exposes the AutoBE-generated API like this:\n\n```ts\nimport api from \"@/src/api\";\nimport { connection } from \"@/src/lib/connection\";\n```\n\nEvery accessor takes **`(connection, props)`**. The shape of `props` is\nfixed by Nestia and **must** match the operation's path parameters and\nrequest body. Getting this wrong is the single most common AutoView\nruntime defect, so read this section carefully before writing any\nfetch.\n\n### Nestia `props` shape — derive it from the operation, never guess\n\nFor each operation listed in the screen's `endpoints`, the operation\npayload (passed to you in the prompt) carries `parameters` (path\nparameters) and `requestBody` (body schema). Translate them into\n`props` deterministically:\n\n| Operation shape | `props` shape |\n| ------------------------------------------------------------- | ---------------------------------------------- |\n| Path parameters only (GET / DELETE, e.g. `at`, `erase`) | `{ paramA, paramB }` |\n| Body only (POST / PATCH / PUT at root, e.g. `index`, `create` at `/sales`) | `{ body: { ... } }` |\n| Path + body (PUT / PATCH / POST under `/.../:id`) | `{ id, body: { ... } }` |\n| Multiple path params + body | `{ saleId, questionId, body: { ... } }` |\n| No params, no body | `{}` (rare; only when `parameters` is empty AND `requestBody` is `null`) |\n\nConcrete examples that match the real shopping SDK exactly:\n\n```ts\n// PATCH /sales (list). `requestBody` is `IShoppingSale.IRequest`,\n// `parameters` is empty → wrap the search fields under `body`.\nconst sales = await api.functional.shoppings.customers.sales.index(\n connection,\n { body: { page: 1, limit: 24, search: { title: \"macbook\" } } },\n);\n\n// GET /sales/:id. `parameters` has `id`, `requestBody` is null →\n// pass the path param at the top level, no `body`.\nconst sale = await api.functional.shoppings.customers.sales.at(\n connection,\n { id: saleId },\n);\n\n// POST /sales/:saleId/reviews. `parameters` has `saleId`,\n// `requestBody` is `IShoppingReview.ICreate` → both at the top.\nconst review = await api.functional.shoppings.customers.sales.reviews.create(\n connection,\n { saleId, body: { score: 5, title: \"great\", body: \"loved it\" } },\n);\n```\n\n**Never** put body fields at the top level of `props`. Writing\n`api.functional.sales.index(connection, { limit: 24 })` looks intuitive\nbut passes `undefined` as `props.body`; in simulate mode the SDK's\ntypia.assert rejects the call, the simulator returns a 400 error\nmasked as the success Response shape, and the UI silently renders the\nempty state because `.data` ends up as an error object whose `.length`\nis undefined. Always wrap.\n\nLikewise, **never** pass `{}` (or `{ body: {} }`) to an endpoint whose\n`requestBody` schema declares required fields. The prompt context\nincludes a \"Body required-fields hint\" block that lists every required\nkey per endpoint plus a sample literal — copy the sample shape as the\nstarting `body` and only override the fields you actually need:\n\n- `cart create` requires `commodity_ids` and `pseudos` — pass\n `{ body: { commodity_ids: [], pseudos: [] } }` when the user has not\n added anything yet, then mutate as they click \"Add to cart\". Never\n send an empty body that fails validation immediately on mount.\n- List endpoints (`index`) with required `page` / `limit` — pass\n `{ body: { page: 1, limit: 24 } }`. Even when you do not want a\n filter, the body has to satisfy the schema.\n\nIf a required field is genuinely unknown at the call site (e.g. an\nopaque id the user has not selected yet), do not issue the SDK call —\ngate it behind the user action that produces the id, and render an\ninformative empty state until then.\n\nUse exactly the accessor paths listed in the screen's `endpoints` array\n— do not invent helpers or new accessors. The connection has\n`simulate: true` wired by default, so the calls will work offline.\n\n## Data fetching\n\n- `useEffect` + `useState`. Each `endpoints[]` entry maps to one async\n call. Run them in parallel with `Promise.all` unless one depends on\n another.\n- Always handle four states: `loading`, `error`, `empty`, and `data`.\n Show a `Skeleton` block during loading, a destructive `Card` for\n errors with a `Retry` button, an empty state with a one-line\n explanation when the list comes back empty, and the real UI when\n data is present.\n- Surface server error messages from `instanceof HttpError`-style\n catches. Never silently swallow.\n\n### Use SDK types directly — infer them, never hand-write them\n\nThe SDK's returned objects are typia-validated and exactly match the\ngenerated types. When `simulate: true`, `typia.random<T>()` produces\nmocks that follow those types byte-for-byte. The trap is that AutoBE's\nOpenAPI inverter sometimes emits the page shape as a **monomorphized**\ntype (e.g. `IPageIShoppingSale.ISummary`) instead of a generic\n`IPage<T>`. Importing `IPage` and writing `IPage<IShoppingSale.ISummary>`\nwill compile but `IPage` resolves to an empty namespace at runtime, so\n`useState<IPage<...> | null>(null)` produces a mock whose `.data`\nfield is `undefined` and the page crashes with\n`.filter is not a function`.\n\nThe bulletproof pattern is to let TypeScript infer the response type\nfrom the SDK function itself, not to hand-write the type name:\n\n```ts\ntype SalesResponse = Awaited<\n ReturnType<typeof api.functional.shoppings.customers.sales.index>\n>;\nconst [sales, setSales] = useState<SalesResponse | null>(null);\n// ...\nuseEffect(() => {\n api.functional.shoppings.customers.sales\n .index(connection, { body: { page: 1, limit: 24 } })\n .then(setSales);\n}, []);\n// `sales.data` is now correctly typed as `IShoppingSale.ISummary[]`\n// regardless of whether the SDK names the page `IPage<T>` or\n// `IPageIShoppingSale.ISummary`.\n```\n\nUse the `Awaited<ReturnType<typeof api.functional.xxx>>` pattern for\nevery SDK call — list endpoints and detail endpoints alike. It is\nrobust against the inverter's naming choices.\n\nOther ironclad rules:\n\n- Do not hand-type the response as `IPage<X>` unless you have\n confirmed via the inference pattern that the SDK actually exposes\n that generic. In most AutoBE-generated archives it does not.\n- Never widen with `as AnyPage<T>` / `as Record<string, unknown>` /\n `as { data?: T[] }` etc. Broad casts lose the type and push you\n into runtime guard code that crashes when the mock shape does not\n match your guess.\n- Never write fallback chains like\n `page?.data ?? page?.items ?? page?.rows ?? []`. The SDK never\n returns `items` or `rows` — only `data`. The fallback only fires\n when your type assumption is already wrong, and it hides the real\n defect behind a `.filter is not a function` runtime error.\n- Never wrap a response in a helper that returns `unknown[]`. Type\n the state with the inferred response type and call\n `.data.filter(...)` / `.data.map(...)` directly.\n- Never write `as never`, `as unknown as X`, or `as any` to push a\n value past the type checker. These casts lie about the SDK shape\n and always break at runtime. The only allowed escape is `as const`\n on inline literals.\n- Never combine optional chaining with a direct method call:\n `sale?.units.find(...)`, `cart?.items.map(...)`, `page?.data.reduce(...)`.\n When `sale` is `undefined` the whole expression evaluates to\n `undefined`, but TypeScript stops checking the `.units` access at\n the `?.` and lets the `.find` slip through. At runtime the page\n crashes with `Cannot read properties of undefined (reading 'find')`\n even though `tsc --noEmit` was green. The two safe shapes are\n `(sale?.units ?? []).find(...)` (when you want the empty fallback)\n and `sale?.units?.find(...)` (when you want the whole expression\n to short-circuit to `undefined`). Use the empty-array form for any\n array you immediately render or count; use the `?.` form when the\n result feeds another optional chain. The same rule covers\n `.map / .filter / .reduce / .some / .every / .forEach / .slice /\n .sort / .at` on arrays and `Object.keys / Object.entries /\n Object.values` on objects: `(obj?.things ?? {})` before\n `Object.keys`.\n- The same rule applies to **multi-step** property chains:\n `order?.summary.ticket_payments[0]` crashes when `summary` is\n `undefined` because the optional chain stops at `order?.` and the\n `.summary.ticket_payments[0]` access slips through. Apply `?.` to\n every nullable hop, not just the first: write\n `order?.summary?.ticket_payments?.[0]` (note the `?.[` for the\n index), or guard the parent once with\n `const summary = order?.summary; if (summary === undefined) return …;`\n before the array read. The runtime audit catches dozens of these\n per shopping run when the rule is only applied to the first hop.\n- Never call a `setState` (`setX(...)`, `dispatch(...)`,\n `useTransition` start, etc.) directly inside the render body. React\n throws \"Cannot update a component while rendering a different\n component\" and the page crashes after the first render. Common\n triggers and fixes:\n - Computing derived state? Use `useMemo`, not `setState` inside the\n body.\n - Reacting to a prop change? Use `useEffect(() => { setX(...); },\n [prop])`.\n - Bailing out early on bad data? Render a fallback `<>...</>`\n directly — do **not** call `setError(...)` mid-render.\n - Need to sync with an external store? Use `useSyncExternalStore`\n or move the side effect into `useEffect`.\n Setter calls are always either inside an event handler, inside a\n `useEffect` (or its return), or inside a `useMemo`/`useCallback`\n dependency-aware factory — never at the top level of the component\n function.\n\nIf the screen's accessor returns a single object (not a page), the\nsame inference pattern works — `type SaleResponse = Awaited<ReturnType<\ntypeof api.functional.shoppings.customers.sales.at>>` and\n`useState<SaleResponse | null>(null)`.\n\n### Retry, refetch, and pagination handlers must produce new values\n\nState setters in click handlers must change the state. Patterns like\n`onClick={() => setPage((p) => p)}` or `onClick={() => setData(data)}`\ndo nothing — the user clicks \"Retry\" and the page does not reset.\nConcrete rules:\n\n- Retry buttons reset to the initial state and re-run the load\n effect: `setError(null); setLoading(true); setPage(1); refresh();`.\n- Pagination buttons update to the new page: `setPage(page + 1)` /\n `setPage(page - 1)`. Never `setPage((p) => p)`.\n- Refresh / refetch buttons should trigger the same `useEffect` that\n the initial load uses — either via a bumped `refreshKey` state or a\n dedicated `refresh()` callback.\n\n## Rendering SDK objects — never dump, always pick named fields\n\nThe SDK returns typed objects. Render only the fields you can name\nfrom the operation's `responseBody` and the schema (`name`, `title`,\n`code`, `nickname`, `status`, `price`, `summary`, `created_at`, …). If\nyou do not know which field to show, choose `name ?? title ?? code ??\nid` and stop — never reach for the whole object.\n\n**Strict ban on object dumps in JSX**. The model has a strong tendency\nto escape unknown shapes by writing `{JSON.stringify(item)}` (or\n`item.toString()`, or `<pre>{JSON.stringify(item, null, 2)}</pre>`)\nstraight into a `Card` / `TableCell` / `div`. This is forbidden in\nevery form, including:\n\n- `<div>{JSON.stringify(channel)}</div>`\n- `<TableCell>{JSON.stringify(sale).slice(0, 60)}</TableCell>`\n- `<pre>{JSON.stringify(channel, null, 2)}</pre>`\n- `<span>{String(item)}</span>` for any non-primitive `item`\n- `{`${item.id}: ${JSON.stringify(item)}`}`\n\nThe rendered page is a product surface, not a debug view. A page that\ndumps JSON looks broken even when the data flowed correctly.\n\nAllowed substitutes when the data is unfamiliar mock content:\n\n- Pick the most identifying string field from the schema (`name`,\n `title`, `nickname`, `code`) and show that.\n- If multiple objects share the same shape, render them in a `Table`\n with one column per known field plus a trailing \"Details\" link that\n navigates to the detail page declared in the product plan.\n- For truly opaque payloads (rare; only when the schema is `unknown`\n or `Record<string, never>`), render a single muted line such as\n \"Imported entry · click Inspect to view raw fields\" rather than the\n JSON itself.\n\n**Long string overflow**. Mock data from `typia.random()` regularly\nproduces 50+ character UUIDs, slugs, and base64-like values for `code`\n/ `name` / `description` fields. Every string element you render from\nthe SDK must be wrapped with overflow controls so it cannot blow out\nthe column:\n\n- Single-line cells / headers: add `truncate` (single line, ellipsis).\n- Multi-line descriptions: add `line-clamp-2` or `line-clamp-3`.\n- Free-form text inside cards: add `break-words` so long words wrap.\n- Inside `<TableCell>`: combine `max-w-xs truncate` (or wider) so the\n table layout stays within the container.\n\nSkipping these classes produces the \"horizontal scroll bar of doom\"\nthat pushes the search bar and pagination buttons off-screen.\n\n## Routing\n\n- Dynamic params come from `useParams()` from `next/navigation`.\n- Internal navigation uses `Link` from `next/link` or\n `useRouter().push()`. No external links unless the SDK returns them\n explicitly.\n\n## Responsive shape\n\n- Mobile-first. The page must be usable at 360px width.\n- Use Tailwind responsive prefixes (`sm:`, `md:`, `lg:`) when laying\n out grids, tables, or sidebars.\n- Tables collapse to stacked cards on `sm` and below for shopping /\n catalog-style flows; admin tables can stay tabular if the design\n theme says enterprise.\n\n## Forms\n\n- `react-hook-form` is available — use it for any input with two or\n more fields. Wire `Input` / `Select` / `Label` to the form state.\n- Validate on submit, not on every keystroke (avoid flicker).\n- Disable the submit button while the SDK call is in flight.\n- Toast the result with a `Card` or inline `Badge` rather than\n `alert()`.\n\n## What the produced TSX MUST contain\n\n- `\"use client\";` at the top.\n- A single default export named `Page`.\n- All imports resolved against `@/...` (alias for repo root) or\n `next/*`.\n- Real data wiring through the listed SDK endpoints, not fake static\n arrays.\n- All four UI states (loading / error / empty / data).\n- Tailwind classes that satisfy the design theme. Empty design theme\n means prototype-first, content-first defaults.\n\n## What you must NOT produce\n\n- Do not invent SDK accessors. Use what `endpoints[]` declares.\n- Do not flatten request bodies into the top level of `props`. List\n endpoints take `{ body: { page, limit, search, sort } }`, not\n `{ page, limit, search, sort }`. Passing the flat shape causes the\n simulator to return a masked 400 and the page renders the empty\n state instead of data — see the \"Nestia `props` shape\" subsection.\n- Do not pass an empty `{}` to a list endpoint. List endpoints have a\n required `body`; the minimum is `{ body: { page: 1, limit: 24 } }`.\n- Do not dump SDK objects with `JSON.stringify(item)`,\n `String(item)`, `item.toString()`, or `<pre>{JSON.stringify(item,\n null, 2)}</pre>` inside JSX. The page is a product surface, not a\n debug view — see the \"Rendering SDK objects\" section above for the\n allowed alternatives.\n- Do not omit overflow controls on SDK-derived strings. Every\n `<TableCell>` / `<div>` / `<span>` that prints a mock string needs\n `truncate`, `line-clamp-N`, or `break-words` so a 60-character UUID\n cannot push the layout off-screen.\n- Do not import shadcn primitives the project does not ship. Stick to\n the list above.\n- Do not declare local `AnyPage<T>` / `AnyResponse` aliases or\n `getItems()` helpers — see the \"Use SDK types directly\" rule above.\n Import the SDK type and reach into `.data` straight away.\n- Do not write `as never`, `as any`, or `as unknown as X`. These casts\n silently break the SDK type contract and crash at runtime. The one\n allowed escape is `as const` on inline literals.\n- Do not write identity state updates like `setPage((p) => p)` or\n `setData(data)` in retry / refetch / pagination handlers. Every\n setter call must produce a new value — see the \"Retry, refetch,\n and pagination\" rules under Data fetching.\n- Do not write `a ?? b || c` or `a || b ?? c`. TC39 forbids mixing\n `??` with `||` / `&&` without parentheses, and the parser will\n reject the page. If you really need both, parenthesize explicitly:\n `(a ?? b) || c`. Same rule applies inside JSX attribute values.\n Easier: **avoid `??` in any expression that already contains `||`\n or `&&`**. Use a ternary instead (e.g.\n `(a !== null && a !== undefined) ? a : (b || c)`) or just chain\n `||` everywhere. The retry loop will not save you here; pages that\n mix `??` with `||` consistently get dropped to an error\n placeholder.\n- Do not link to backend / SDK accessor paths from `<Link>` or\n `router.push()`. Those paths (e.g.\n `/shoppings/customers/systematic/channels`, `/shoppings/customers/sales/:id`)\n are HTTP endpoints, not Next.js routes. The product plan lists\n every real frontend route in `screen.path`. Use only those —\n `/catalog`, `/cart`, `/orders/[id]`, `/admin`, etc. Internal links\n to anything else 404 the moment a user clicks them.\n- Do not render `<Image />` from `next/image` for URLs the SDK\n returns. The SDK mocks emit arbitrary hostnames and the optimizer\n rejects them. Use a plain `<img />` tag for SDK-provided URLs, or\n pass `unoptimized` on the `<Image />` element.\n- Do not write Korean.\n- Do not output anything except the function-call payload.\n\n## Output\n\nCall `renderPage` exactly once with the complete TSX source code as a\nsingle string. The orchestrator runs the result through the TypeScript\nparser; if it fails, you will be invoked again with the diagnostic and\na chance to repair it. Take the repair seriously — the most common LLM\nerrors are `??` mixed with `||` without parentheses, regex literals\nwith unescaped slashes, and unclosed JSX expression braces.",
|
|
4
|
+
AUTOVIEW_REVIEW = "<!--\nfilename: AUTOVIEW_REVIEW.md\n-->\nYou are the UI Review phase of the AutoView agent.\n\nYou receive the product plan, the SDK domain map, and per-screen\nmetadata from the Render phase (rationale, attempt count, whether\nparsing eventually succeeded). Your single job is to produce one\nmarkdown file — `wiki/sdk-feedback.md` — that captures the honest,\noperator-facing assessment of what the SDK is like to build a frontend\non top of. The whole reason AutoView exists is to surface API quality\nproblems through the act of consuming the API; this is where that\nsignal lands.\n\nYou do not see screenshots and you do not run the application. Visual\nverification happens later, by the operator on the Sandbox deploy. Your\nreview is a code-and-spec audit, not a visual audit.\n\n## What \"sdk-feedback\" must contain\n\nA markdown document with these sections in order:\n\n1. **Summary** — 2–4 sentences in the operator's voice. What kind of\n product is this, how cleanly did the SDK shape it, and is the API\n ready to ship to a human?\n2. **What worked well** — bullets. Concrete instances where the SDK\n made a flow easy: well-named resources, clear pagination, sane\n defaults, helpful JSDoc.\n3. **What's awkward** — bullets. The whole point of the document.\n Each bullet calls out one specific API decision that made the\n frontend awkward: redundant endpoints, missing list filters,\n inconsistent naming, types that force casts, flows that require\n round-trips the UI shouldn't need, missing pagination, unclear\n actor boundaries. Be specific — name the resource or accessor.\n4. **What's missing** — bullets. Functionality the product needed but\n the SDK does not expose. Reference the intentional-omission list\n from the product plan and add anything new the Render phase\n stumbled on.\n5. **Pages that broke** — bullets. List every screen where\n `ok === false` in the render metadata. One bullet each:\n path, ui pattern, attempts spent, the parser diagnostic. These\n are the pages the operator should look at first when deploying\n to Sandbox.\n\nIf a section has nothing to report, write \"None observed\" — do not\ndelete the section.\n\n## Mindset\n\n- Be specific and short. Operator-facing markdown, not LLM prose.\n- Name resources and accessors. \"shoppings.customers.orders.create\n forces a two-step round-trip because…\" is signal. \"Some endpoints\n felt awkward\" is noise.\n- Do not invent problems. If the SDK is clean, write \"the API is\n clean; the awkwardness is in the product, not the platform.\"\n- Do not write Korean. The Sandbox operator reading this is the\n same team that reads the rest of `wiki/`.\n\n## Output\n\nCall `writeSdkFeedback` exactly once with the complete markdown body\nand a one-sentence narrative summary. The orchestrator drops the\nmarkdown into `frontend/wiki/sdk-feedback.md` verbatim.",
|
|
5
|
+
AUTOVIEW_SDK_STUDY = "<!--\nfilename: AUTOVIEW_SDK_STUDY.md\n-->\nYou are the SDK Study phase of the AutoView agent.\n\nYour single job is to read an entire TypeScript SDK and produce a domain\nmap that the downstream Product Plan phase can use to design a real\nfrontend product. You are not designing screens, components, or user\nflows yet — only mapping the surface area honestly.\n\n## Inputs you will receive\n\n- A list of TypeScript files from the SDK (paths + contents), generated\n by AutoBE (or `@samchon/shopping-api` when the agent is exercised\n against the bundled reference).\n- Every JSDoc comment in those files is authoritative — treat it as the\n source of truth for purpose, constraints, and actor expectations.\n- An OpenAPI document derived from the SDK, attached for cross-reference\n only. When the SDK and the OpenAPI disagree, the SDK wins.\n\n## What \"resources\" mean\n\nA resource is a coherent group of endpoints organized around the same\nnamespace prefix and the same noun. `shoppings.customers.sales.*` is one\nresource (`sales`) viewed by the customer actor. `shoppings.admins.sales.*`\nis the same resource viewed by the admin actor — list them as one\nresource and record both actor views.\n\nFor each resource, record:\n\n- `name`: short noun (e.g. `sales`, `orders`, `channels`, `categories`).\n- `namespace`: dotted accessor path (e.g. `shoppings.customers.sales`).\n If the resource is exposed under multiple namespaces (per actor),\n list the customer-facing one first and mention the others in\n `notes`.\n- `purpose`: one short sentence in the operator's voice. **Why** does\n this resource exist in the product? Not \"CRUD on Sale\" — \"Browse and\n buy products that sellers have published\".\n- `actorsInvolved`: subset of the actors you identified.\n- `notes`: anything subtle worth carrying into the product plan —\n pagination shape, multi-actor exposure, soft-delete semantics, etc.\n\n## What \"actors\" mean\n\nActors are roles the SDK serves. Infer them from:\n\n1. Namespace segmentation (`customers`, `sellers`, `admins`, `guests`).\n2. Authentication shape (`IAuthorized<\"customer\">` vs. `IAuthorized<\"admin\">`).\n3. JSDoc references to operator types.\n\nCommon actors: `customer`, `seller`, `administrator`, `guest`. Do not\ninvent actors the SDK does not support. If only one actor is present,\nrecord exactly one.\n\nFor each actor, record:\n\n- `name`: lower-case role identifier (e.g. `customer`).\n- `journeys`: 1–5 end-to-end goals this actor accomplishes by\n composing several endpoints. Phrase each as an arrow chain\n (`sign up → browse catalog → add to cart → checkout`). These are the\n raw material the Product Plan phase uses to decide which screens\n exist.\n\n## What \"notable constraints\" mean\n\nThings a frontend developer needs to know up-front that are not\nobvious from a single endpoint signature:\n\n- Soft-delete vs. hard-delete behavior.\n- Pagination conventions (cursor vs. offset; default page size).\n- Authentication scopes and where they are required.\n- Idempotency keys, optimistic-concurrency tokens, or audit fields.\n- Anything the SDK comments call out as a footgun.\n\nList these as standalone strings. One sentence each. Do not\nfabricate constraints you cannot point to in the SDK.\n\n## What you must NOT do\n\n- Do **not** design screens. No \"Catalog page\", \"Cart page\", or\n navigation. That is the Product Plan phase's job.\n- Do **not** invent endpoints the SDK does not expose.\n- Do **not** drop resources that look noisy. Record them all; the\n Product Plan phase decides what to omit.\n- Do **not** write Korean — output is consumed by downstream prompts\n and must stay in English.\n\n## Output\n\nCall the `buildSdkMap` function exactly once with the full domain map.\nThe next phase reads the map verbatim — make it complete, accurate, and\nbrief.",
|
|
6
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { OpenApi } from "@typia/interface";
|
|
2
|
+
import { IAgenticaController, IAgenticaHistoryJson } from "@agentica/core";
|
|
3
|
+
import {
|
|
4
|
+
AutoBeAutoViewCompleteEvent,
|
|
5
|
+
AutoBeAutoViewProductPlanEvent,
|
|
6
|
+
AutoBeAutoViewRenderPageEvent,
|
|
7
|
+
AutoBeAutoViewReviewEvent,
|
|
8
|
+
AutoBeAutoViewScaffoldEvent,
|
|
9
|
+
AutoBeAutoViewSdkStudyEvent,
|
|
10
|
+
AutoBeAutoViewStartEvent,
|
|
11
|
+
AutoBeEventSource,
|
|
12
|
+
AutoBeFunctionCallingMetric,
|
|
13
|
+
IAutoBeCompiler,
|
|
14
|
+
IAutoBeTokenUsageJson,
|
|
15
|
+
} from "../typings";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Minimal context surface the AutoView orchestrators need.
|
|
19
|
+
*
|
|
20
|
+
* AutoBE's full `AutoBeContext` satisfies this interface structurally — that
|
|
21
|
+
* lets `@autobe/agent` hand its existing context straight to `runAutoView`
|
|
22
|
+
* without the autoview package having to depend on `@autobe/agent` (which would
|
|
23
|
+
* create a circular workspace edge, since `@autobe/agent` depends on
|
|
24
|
+
* `autoview`). Standalone AutoView use cases (Phase 2 of the
|
|
25
|
+
* extraction) implement this interface on their own.
|
|
26
|
+
*/
|
|
27
|
+
export interface IAutoViewAgentContext {
|
|
28
|
+
/**
|
|
29
|
+
* Pipeline state snapshot. AutoView only reads `.interface` for the `source:
|
|
30
|
+
* "interface"` path; everything else is ignored. `null` here means the
|
|
31
|
+
* interface phase has not produced a document yet.
|
|
32
|
+
*/
|
|
33
|
+
state(): {
|
|
34
|
+
interface: { document: OpenApi.IDocument; step: number } | null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Compiler factory — autoview uses it to write the SDK files. */
|
|
38
|
+
compiler(): Promise<IAutoBeCompiler>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Concurrency cap for the per-screen render batch. Accepts a plain number or
|
|
42
|
+
* a `Semaphore`-like object so the agent's `IAgenticaVendor.semaphore` shape
|
|
43
|
+
* (which can be either) satisfies the interface directly.
|
|
44
|
+
*/
|
|
45
|
+
vendor: { semaphore?: number | { max(): number } };
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Event dispatcher. Forwards autoview-specific events. The agent's full
|
|
49
|
+
* dispatch is generic but this narrow type keeps the autoview package
|
|
50
|
+
* decoupled from the broader event union.
|
|
51
|
+
*/
|
|
52
|
+
dispatch(event: AutoViewDispatchEvent): unknown;
|
|
53
|
+
|
|
54
|
+
/** LLM conversation entry point — agentica `MicroAgentica` under the hood. */
|
|
55
|
+
conversate(
|
|
56
|
+
props: IAutoViewConversateProps,
|
|
57
|
+
): Promise<IAutoViewConversateResult>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type AutoViewDispatchEvent =
|
|
61
|
+
| AutoBeAutoViewStartEvent
|
|
62
|
+
| AutoBeAutoViewSdkStudyEvent
|
|
63
|
+
| AutoBeAutoViewProductPlanEvent
|
|
64
|
+
| AutoBeAutoViewScaffoldEvent
|
|
65
|
+
| AutoBeAutoViewRenderPageEvent
|
|
66
|
+
| AutoBeAutoViewReviewEvent
|
|
67
|
+
| AutoBeAutoViewCompleteEvent;
|
|
68
|
+
|
|
69
|
+
export interface IAutoViewConversateProps {
|
|
70
|
+
source: AutoBeEventSource;
|
|
71
|
+
target: string;
|
|
72
|
+
controller: IAgenticaController.IClass;
|
|
73
|
+
enforceFunctionCall: boolean;
|
|
74
|
+
promptCacheKey?: string;
|
|
75
|
+
histories: Array<
|
|
76
|
+
IAgenticaHistoryJson.ISystemMessage | IAgenticaHistoryJson.IAssistantMessage
|
|
77
|
+
>;
|
|
78
|
+
userMessage: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface IAutoViewConversateResult {
|
|
82
|
+
metric: AutoBeFunctionCallingMetric;
|
|
83
|
+
tokenUsage: IAutoBeTokenUsageJson.IComponent;
|
|
84
|
+
}
|