diff --git a/apps/web/.github/workflows/deploy.yml b/apps/web/.github/workflows/deploy.yml deleted file mode 100644 index 2dccfd4ec..000000000 --- a/apps/web/.github/workflows/deploy.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Deploy to Vercel - -on: - push: - branches: - - main - - master - pull_request: - branches: - - main - - master - -env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: '18' - cache: 'npm' - - - name: Install Vercel CLI - run: npm install --global vercel@latest - - - name: Pull Vercel Environment Information - run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} - - - name: Build Project Artifacts - run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} - - - name: Deploy Project Artifacts to Vercel - run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} - - - name: Comment PR with deployment URL - if: github.event_name == 'pull_request' - uses: actions/github-script@v6 - with: - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: '✅ Deployed to Vercel! Check your deployment at https://vercel.com' - }) diff --git a/apps/web/REVENUE-PROCESS.md b/apps/web/REVENUE-PROCESS.md index ce938971c..cb475553d 100644 --- a/apps/web/REVENUE-PROCESS.md +++ b/apps/web/REVENUE-PROCESS.md @@ -94,7 +94,7 @@ cd apps/web SCRATCH=/path/to/scratch node scripts/run-revenue-spine.mjs ``` -Truncates scratch logs, runs checkout → signed `checkout.session.completed` webhook → activate → status → pro chat → renew **twice**. MyXstack gate: `myxteam/skills/premium-gate.ts` checks `/api/billing/status`. +Truncates scratch logs, runs checkout → signed `checkout.session.completed` webhook → activate → status → pro chat → renew **twice**, then records `/api/billing/status`. ## Non-goals (this repo slice) diff --git a/apps/web/SITE_STRUCTURE_INVENTORY.md b/apps/web/SITE_STRUCTURE_INVENTORY.md index dd8071160..72717d3ca 100644 --- a/apps/web/SITE_STRUCTURE_INVENTORY.md +++ b/apps/web/SITE_STRUCTURE_INVENTORY.md @@ -17,12 +17,6 @@ Live at https://uvai.io (Vercel project `v0-uvai`). | `/docs/api` | `docs/api/page.tsx` (126 lines) | Static API reference page listing public endpoints (transcribe, extract-events, chat, agents, video metadata). | | `/privacy` | `privacy/page.tsx` | Static privacy policy (last updated May 25, 2026). | | `/terms` | `terms/page.tsx` | Static terms of service. | -| `/dashboard` | `dashboard/page.tsx` | Legacy redirect → `/studio` (preserves query params). | -| `/dashboard/agents` | `dashboard/agents/page.tsx` | Legacy redirect → `/studio`. | -| `/app` | `app/page.tsx` | Legacy redirect → `/studio`. | -| `/features` | `features/page.tsx` | Redirect → `/`. | -| `/playground` | `playground/page.tsx` | Redirect → `/`. | -| `/prototype` | `prototype/page.tsx` (+ `prototype/layout.tsx`) | Legacy interactive-simulation route; redirects → `/studio` ("could be mistaken for a verified workflow"). | Supporting app files: - `layout.tsx` — root layout: fonts (Inter, JetBrains Mono, Space Grotesk via `next/font`), metadata/SEO, `StructuredData` (JSON-LD), `AuthSessionProvider` (next-auth), Vercel Analytics + Speed Insights (prod only), global mesh/noise background divs. @@ -145,17 +139,13 @@ All delegate to handlers in `src/lib/studio/routes.ts`. `runtime = 'nodejs'`, `f - `landing/` — **orphaned, zero imports** (deletion candidate): `HeroSection`, `BentoFeatures`, `WorkflowSection`, `TemplatesSection`, `DeveloperSection`, `ContactSection`, `LandingNav`, `LandingFooter`. ### Billing -- `billing/ProCheckoutButton.tsx`, `billing/ProRenewPanel.tsx`, `billing/CheckoutSuccessActivator.tsx`, `billing/BillingStatusBanner.tsx`. - -### Dashboard (legacy skins — mostly redirect to Studio now) -- `dashboard/DashboardCanvasView.tsx`, `dashboard/DashboardSplitView.tsx`, `dashboard/VideoCanvasStage.tsx`, `dashboard/panels.tsx`. -- Top-level: `AgentDashboard.tsx`, `AgentFlowVisualizer.tsx`, `OrchestratorRail.tsx`, `AnalysisPanel.tsx`, `EventList.tsx`, `ResultsViewer.tsx`, `TranscriptViewer.tsx`, `InteractiveTranscript.tsx`, `PipelineProgress.tsx`, `TracePanel.tsx`, `ConsensusIndicator.tsx`, `PreferencesPanel.tsx`, `FeedbackWidget.tsx`, `video-generator.tsx`, `StructuredData.tsx` (JSON-LD), `AuthSessionProvider.tsx`. +- `billing/ProCheckoutButton.tsx`, `billing/ProRenewPanel.tsx`, `billing/CheckoutSuccessActivator.tsx`. ### UI primitives (`ui/`) -Radix-based + custom: `Button`, `Card`, `Input`, `Badge`, `alert`, `empty`, `field`, `label`, `separator`, `tabs`, `textarea`, `resizable` (react-resizable-panels wrapper), `SuggestedPrompts`, `index.ts` barrel. Styled via `components.json` (shadcn convention). +Radix-based + custom: `Button`, `alert`, `empty`, `field`, `label`, `separator`. Styled via `components.json` (shadcn convention). ### Tests -`components/__tests__/`: `OneLoopStudio.gate.test.tsx`, `OneLoopStudio.build-live.test.tsx`, `OneLoopStudio.job-strip.test.tsx`, `OneLoopStudio.three-panel.test.tsx`, `GroundedSpecReview.test.tsx`, `StudioAuthNavLink.test.tsx`; `home/`: `HomePasteForm.test.tsx`, `HomeProCheckout.test.tsx`; `ui/resizable.test.tsx`. +`components/__tests__/`: `OneLoopStudio.gate.test.tsx`, `OneLoopStudio.build-live.test.tsx`, `OneLoopStudio.job-strip.test.tsx`, `OneLoopStudio.three-panel.test.tsx`, `GroundedSpecReview.test.tsx`, `StudioAuthNavLink.test.tsx`; `home/`: `HomePasteForm.test.tsx`, `HomeProCheckout.test.tsx`. --- @@ -204,7 +194,7 @@ Radix-based + custom: `Button`, `Card`, `Input`, `Badge`, `alert`, `empty`, `fie **State** - `src/store/dashboard-store.ts` (632) — **Zustand + persist.** Videos, pack records, `processVideo` (durable run → complete only on verified evidence + quality gate), chat, selection. -- `src/store/action-agent-store.ts`, `src/store/dashboard-types.ts`. +- `src/store/dashboard-types.ts`. **Infra/misc** - `ssrf-guard.ts` — URL fetch allowlisting. diff --git a/apps/web/playwright/smoke.spec.ts b/apps/web/playwright/smoke.spec.ts index 98ed9d2f9..c80480450 100644 --- a/apps/web/playwright/smoke.spec.ts +++ b/apps/web/playwright/smoke.spec.ts @@ -59,7 +59,7 @@ test.describe('UVAI Production-Path Smoke Suite', () => { await expect(page.getByText('Universal Video Action Intelligence')).toBeVisible(); await expect( page.getByRole('heading', { - name: /Turn a YouTube URL into a hashed Video Pack in Studio\./i, + name: 'Your videos know what to do next.', }) ).toBeVisible(); await expect(page.getByText(/Transcript quality varies by source/i)).toBeVisible(); diff --git a/apps/web/scripts/run-revenue-spine.mjs b/apps/web/scripts/run-revenue-spine.mjs index 3475282c4..70112e110 100644 --- a/apps/web/scripts/run-revenue-spine.mjs +++ b/apps/web/scripts/run-revenue-spine.mjs @@ -291,17 +291,6 @@ async function captureAppLaunch() { } async function captureSkillsInstallGate(cookie) { - const { spawnSync } = await import('node:child_process'); - const myxteamDir = resolve(process.cwd(), '../../../../MyXstack/myxteam'); - const test = spawnSync( - 'pnpm', - ['exec', 'vitest', 'run', 'src/gateway/server-methods/skills-premium-gate.test.ts'], - { cwd: myxteamDir, encoding: 'utf8' }, - ); - log('marketplace-gate.log', `skills.install gate tests exit=${test.status}`); - if (test.stdout) log('marketplace-gate.log', test.stdout.trim().split('\n').slice(-6).join('\n')); - if (test.status !== 0) throw new Error('skills-premium-gate tests failed'); - let status = { plan: 'unknown', status: 'unknown', renewalEligible: false }; for (let attempt = 1; attempt <= 3; attempt++) { try { diff --git a/apps/web/src/app/api/transcribe/route.ts b/apps/web/src/app/api/transcribe/route.ts index 3d375e956..fd918fb5a 100644 --- a/apps/web/src/app/api/transcribe/route.ts +++ b/apps/web/src/app/api/transcribe/route.ts @@ -6,46 +6,10 @@ export const runtime = 'nodejs'; export const maxDuration = 120; /** - * POST /api/transcribe - * - * Multi-strategy transcript extraction with comprehensive error handling: - * 1. YouTube captions via backend (fast + free) - with circuit breaker - * 2. Gemini fallback + Google Search - with exponential backoff - * 3. OpenAI Responses API with web search - with rate limit handling - * 4. Direct audio STT via OpenAI Whisper - * - * Every error response carries a stable machine-readable `code`. The JSON-parse - * and catch-all branches also fix their `error` string, because those paths can - * carry raw upstream provider text (account IDs, quota state, partial API keys) - * — that text is logged server-side only. - * - * The `!result.success` branches keep `fetchTranscript`'s own message in `error`. - * That holds only while every value it can return is a fixed, app-authored - * literal — verified as of this change, all seven are. Three were not, and were - * corrected at the source in `transcription-service.ts` rather than masked here: - * - the caller-supplied `audioUrl`'s HTTP status, which made this route a - * cross-origin probe, - * - a message that varied on whether provider API keys were configured, which - * disclosed server configuration, and - * - the SSRF guard's rejection reason, which named which guard rule fired and - * so leaked hostname-blocklist policy. - * All three are logged server-side instead. - * - * Keep that invariant when adding a strategy: anything interpolated into a - * `fetchTranscript` error reaches the client verbatim through the branches - * below. `${}` in an error value is the thing to look for. - * - * What this deliberately does NOT claim: the *choice among* those literals is - * still informative. `Rejected audioUrl` versus `Could not retrieve the audio - * file` tells a caller whether their host cleared the SSRF guard — i.e. whether - * it resolves to a public address. That residue is inherent to a guard that - * refuses some inputs and attempts others, it is far coarser than naming the - * rule, and this route is session-gated (`apps/web/src/lib/auth-paths.ts`), so - * it is accepted rather than papered over. - * - 400: Invalid input (missing URL, malformed JSON) — `input_required` / `invalid_json` - * - 429: Rate limited (implement backoff or upgrade service plan) — `rate_limited` - * - 500: Service unavailable (check API keys and billing) — `billing_not_configured` - * - 503: Cascading failures (all fallback strategies exhausted) — `transcription_unavailable` + * POST /api/transcribe — parse JSON, then `fetchTranscript`. + * 400 `input_required` / `invalid_json`, 429 `rate_limited`, + * 500 `billing_not_configured` or `transcription_failed`, + * 503 `transcription_unavailable`. */ export async function POST(request: Request) { try { diff --git a/apps/web/src/app/docs/api/page.tsx b/apps/web/src/app/docs/api/page.tsx index f2ebfc244..fbbce7a0b 100644 --- a/apps/web/src/app/docs/api/page.tsx +++ b/apps/web/src/app/docs/api/page.tsx @@ -23,11 +23,11 @@ const ENDPOINTS: Endpoint[] = [ { method: 'POST', path: '/api/transcribe', summary: 'Fetch a transcript for a YouTube URL.', body: '{ "url": "https://youtu.be/..." }' }, { method: 'POST', path: '/api/extract-events', summary: 'Extract typed events from a transcript.' }, { method: 'POST', path: '/api/chat', summary: 'Conversational query over an analyzed video.' }, - { method: 'GET', path: '/api/video', summary: 'List recently analyzed videos. [DEPRECATED — use /api/video/pack]' }, + { method: 'GET', path: '/api/video', summary: 'Health probe for the video API.' }, { method: 'POST', path: '/api/video', summary: 'Register a new video for analysis. [DEPRECATED — use /api/video/pack]' }, { method: 'GET', path: '/api/video/search', summary: 'Semantic search across stored videos.' }, - { method: 'GET', path: '/api/dashboard', summary: 'Dashboard aggregates and recent runs.' }, - { method: 'POST', path: '/api/dashboard', summary: 'Mutate dashboard state (pin, archive, etc.).' }, + { method: 'GET', path: '/api/dashboard', summary: 'Backend health as status and workflow metrics.' }, + { method: 'POST', path: '/api/dashboard', summary: 'Request a backend dashboard embed URL.' }, { method: 'GET', path: '/api/training/status', summary: 'Current status of training/embedding jobs.' }, { method: 'POST', path: '/api/training/trigger', summary: 'Trigger a training/embedding job.' }, { method: 'POST', path: '/api/video/pack', summary: 'Emit a hashed Video Pack from a YouTube URL.', body: '{ "url": "https://www.youtube.com/watch?v=..." }' }, @@ -58,8 +58,9 @@ export default function ApiDocsPage() {
The legacy /api/pipeline and /api/pipeline/stream
- routes are retired. GET and POST /api/video remain
- for compatibility but are deprecated; use POST /api/video/pack.
+ routes are retired. GET /api/video is a health probe.
+ POST /api/video remains for compatibility but is deprecated;
+ use POST /api/video/pack.