Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 0 additions & 52 deletions apps/web/.github/workflows/deploy.yml

This file was deleted.

2 changes: 1 addition & 1 deletion apps/web/REVENUE-PROCESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
18 changes: 4 additions & 14 deletions apps/web/SITE_STRUCTURE_INVENTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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`.

---

Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion apps/web/playwright/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
11 changes: 0 additions & 11 deletions apps/web/scripts/run-revenue-spine.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
44 changes: 4 additions & 40 deletions apps/web/src/app/api/transcribe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 6 additions & 5 deletions apps/web/src/app/docs/api/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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=..." }' },
Expand Down Expand Up @@ -58,8 +58,9 @@ export default function ApiDocsPage() {
</p>
<p className="mt-3 max-w-2xl text-sm text-ink/50">
The legacy <code>/api/pipeline</code> and <code>/api/pipeline/stream</code>
routes are retired. <code>GET</code> and <code>POST /api/video</code> remain
for compatibility but are deprecated; use <code>POST /api/video/pack</code>.
routes are retired. <code>GET /api/video</code> is a health probe.
<code>POST /api/video</code> remains for compatibility but is deprecated;
use <code>POST /api/video/pack</code>.
</p>
</header>

Expand Down
10 changes: 0 additions & 10 deletions apps/web/types/styled-jsx.d.ts

This file was deleted.

Loading