1666 lines
46 KiB
YAML
1666 lines
46 KiB
YAML
# Design Document v001
|
|
# Voice Recording App with AI-Powered Summarization and Automatic App Generation
|
|
# Created: 2024-12-19T06:00:00Z
|
|
|
|
workflow_version: "v001"
|
|
feature: "Voice recording app with AI-powered summarization and automatic app generation"
|
|
created_at: "2024-12-19T06:00:00Z"
|
|
updated_at: "2024-12-19T06:00:00Z"
|
|
approved_at: null
|
|
status: draft
|
|
revision: 1
|
|
revision_notes: "Initial design document"
|
|
|
|
# ============================================================================
|
|
# EXTERNAL DEPENDENCIES (Pre-existing entities)
|
|
# ============================================================================
|
|
external_dependencies:
|
|
models: []
|
|
api_endpoints: []
|
|
components: []
|
|
|
|
# ============================================================================
|
|
# LAYER 1: DATA MODELS
|
|
# ============================================================================
|
|
data_models:
|
|
# USER MODEL
|
|
- id: model_user
|
|
name: User
|
|
description: "Application user account with email/password authentication"
|
|
table_name: users
|
|
fields:
|
|
- name: id
|
|
type: uuid
|
|
constraints: [primary_key]
|
|
description: "Unique user identifier"
|
|
- name: email
|
|
type: string
|
|
constraints: [unique, not_null, indexed]
|
|
description: "User email address for login"
|
|
- name: name
|
|
type: string
|
|
constraints: [not_null]
|
|
description: "User's display name"
|
|
- name: password_hash
|
|
type: string
|
|
constraints: [not_null]
|
|
description: "Bcrypt hashed password"
|
|
relations: []
|
|
indexes:
|
|
- fields: [email]
|
|
unique: true
|
|
name: idx_users_email
|
|
timestamps: true
|
|
soft_delete: false
|
|
validations:
|
|
- field: email
|
|
rule: email
|
|
message: "Invalid email format"
|
|
- field: name
|
|
rule: "min:1"
|
|
message: "Name is required"
|
|
- field: password_hash
|
|
rule: "min:60"
|
|
message: "Invalid password hash"
|
|
|
|
# RECORDING MODEL
|
|
- id: model_recording
|
|
name: Recording
|
|
description: "Voice recording with transcript and summary"
|
|
table_name: recordings
|
|
fields:
|
|
- name: id
|
|
type: uuid
|
|
constraints: [primary_key]
|
|
description: "Unique recording identifier"
|
|
- name: user_id
|
|
type: uuid
|
|
constraints: [foreign_key, not_null, indexed]
|
|
description: "Owner of this recording"
|
|
- name: title
|
|
type: string
|
|
constraints: [not_null]
|
|
description: "Recording title (auto-generated or user-edited)"
|
|
- name: audio_file_path
|
|
type: string
|
|
constraints: [not_null]
|
|
description: "Path to audio file in MinIO/S3"
|
|
- name: duration
|
|
type: integer
|
|
constraints: [not_null]
|
|
description: "Recording duration in seconds"
|
|
- name: transcript
|
|
type: text
|
|
constraints: []
|
|
description: "Full transcript from Whisper STT"
|
|
- name: summary
|
|
type: text
|
|
constraints: []
|
|
description: "AI-generated summary from Gemini"
|
|
- name: is_transcribing
|
|
type: boolean
|
|
constraints: [not_null, default]
|
|
default: false
|
|
description: "Whether currently transcribing"
|
|
relations:
|
|
- type: belongs_to
|
|
target: model_user
|
|
foreign_key: user_id
|
|
on_delete: cascade
|
|
indexes:
|
|
- fields: [user_id, created_at]
|
|
unique: false
|
|
name: idx_recordings_user_created
|
|
timestamps: true
|
|
soft_delete: false
|
|
validations:
|
|
- field: duration
|
|
rule: "min:1"
|
|
message: "Duration must be at least 1 second"
|
|
|
|
# GENERATED APP MODEL
|
|
- id: model_generated_app
|
|
name: GeneratedApp
|
|
description: "AI-generated application from recording content"
|
|
table_name: generated_apps
|
|
fields:
|
|
- name: id
|
|
type: uuid
|
|
constraints: [primary_key]
|
|
description: "Unique app identifier"
|
|
- name: user_id
|
|
type: uuid
|
|
constraints: [foreign_key, not_null, indexed]
|
|
description: "Owner of this app"
|
|
- name: recording_id
|
|
type: uuid
|
|
constraints: [foreign_key, not_null, indexed]
|
|
description: "Source recording that triggered generation"
|
|
- name: title
|
|
type: string
|
|
constraints: [not_null]
|
|
description: "App title from AI analysis"
|
|
- name: description
|
|
type: text
|
|
constraints: []
|
|
description: "App description"
|
|
- name: html_content
|
|
type: text
|
|
constraints: [not_null]
|
|
description: "Complete HTML/CSS/JS for iframe rendering"
|
|
- name: prd_content
|
|
type: text
|
|
constraints: []
|
|
description: "Product Requirements Document (PRD) generated by AI"
|
|
- name: ui_ux_design
|
|
type: text
|
|
constraints: []
|
|
description: "UI/UX design notes from AI"
|
|
- name: app_type
|
|
type: string
|
|
constraints: []
|
|
description: "Type of app determined by AI (e.g., todo, calculator, form)"
|
|
- name: status
|
|
type: enum
|
|
enum_values: [generating, completed, failed]
|
|
constraints: [not_null, default]
|
|
default: generating
|
|
description: "Generation status"
|
|
relations:
|
|
- type: belongs_to
|
|
target: model_user
|
|
foreign_key: user_id
|
|
on_delete: cascade
|
|
- type: belongs_to
|
|
target: model_recording
|
|
foreign_key: recording_id
|
|
on_delete: cascade
|
|
indexes:
|
|
- fields: [user_id, created_at]
|
|
unique: false
|
|
name: idx_apps_user_created
|
|
- fields: [recording_id]
|
|
unique: false
|
|
name: idx_apps_recording
|
|
timestamps: true
|
|
soft_delete: false
|
|
validations: []
|
|
|
|
# ============================================================================
|
|
# LAYER 2: API ENDPOINTS
|
|
# ============================================================================
|
|
api_endpoints:
|
|
# ========================================
|
|
# AUTHENTICATION ENDPOINTS
|
|
# ========================================
|
|
- id: api_register_user
|
|
method: POST
|
|
path: /api/auth/register
|
|
summary: "Register a new user"
|
|
description: "Create a new user account with email and password"
|
|
tags: [auth]
|
|
path_params: []
|
|
query_params: []
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: email
|
|
type: string
|
|
required: true
|
|
validations: [email]
|
|
description: "User email address"
|
|
- name: name
|
|
type: string
|
|
required: true
|
|
validations: ["min:1", "max:100"]
|
|
description: "User display name"
|
|
- name: password
|
|
type: string
|
|
required: true
|
|
validations: ["min:8"]
|
|
description: "Password (min 8 characters)"
|
|
example:
|
|
email: "user@example.com"
|
|
name: "John Doe"
|
|
password: "securepass123"
|
|
responses:
|
|
- status: 201
|
|
description: "User created successfully"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: id
|
|
type: uuid
|
|
- name: email
|
|
type: string
|
|
- name: name
|
|
type: string
|
|
- name: created_at
|
|
type: datetime
|
|
example:
|
|
id: "550e8400-e29b-41d4-a716-446655440000"
|
|
email: "user@example.com"
|
|
name: "John Doe"
|
|
created_at: "2024-12-19T06:00:00Z"
|
|
- status: 400
|
|
description: "Validation error"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "Invalid email or password too short"
|
|
- status: 409
|
|
description: "Email already exists"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "Email already registered"
|
|
depends_on_models: [model_user]
|
|
depends_on_apis: []
|
|
auth:
|
|
required: false
|
|
roles: []
|
|
rate_limit:
|
|
requests: 10
|
|
window: "1h"
|
|
|
|
- id: api_login_user
|
|
method: POST
|
|
path: /api/auth/login
|
|
summary: "Login user"
|
|
description: "Authenticate user with email and password, return session token"
|
|
tags: [auth]
|
|
path_params: []
|
|
query_params: []
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: email
|
|
type: string
|
|
required: true
|
|
validations: [email]
|
|
description: "User email"
|
|
- name: password
|
|
type: string
|
|
required: true
|
|
description: "User password"
|
|
example:
|
|
email: "user@example.com"
|
|
password: "securepass123"
|
|
responses:
|
|
- status: 200
|
|
description: "Login successful"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: user
|
|
type: object
|
|
- name: token
|
|
type: string
|
|
example:
|
|
user:
|
|
id: "550e8400-e29b-41d4-a716-446655440000"
|
|
email: "user@example.com"
|
|
name: "John Doe"
|
|
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
- status: 401
|
|
description: "Invalid credentials"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "Invalid email or password"
|
|
depends_on_models: [model_user]
|
|
depends_on_apis: []
|
|
auth:
|
|
required: false
|
|
roles: []
|
|
rate_limit:
|
|
requests: 20
|
|
window: "1h"
|
|
|
|
- id: api_logout_user
|
|
method: POST
|
|
path: /api/auth/logout
|
|
summary: "Logout user"
|
|
description: "Invalidate current session token"
|
|
tags: [auth]
|
|
path_params: []
|
|
query_params: []
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties: []
|
|
responses:
|
|
- status: 200
|
|
description: "Logout successful"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: success
|
|
type: boolean
|
|
example:
|
|
success: true
|
|
depends_on_models: []
|
|
depends_on_apis: []
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
|
|
- id: api_get_current_user
|
|
method: GET
|
|
path: /api/auth/me
|
|
summary: "Get current user"
|
|
description: "Get currently authenticated user information"
|
|
tags: [auth]
|
|
path_params: []
|
|
query_params: []
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties: []
|
|
responses:
|
|
- status: 200
|
|
description: "User data"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: id
|
|
type: uuid
|
|
- name: email
|
|
type: string
|
|
- name: name
|
|
type: string
|
|
- name: created_at
|
|
type: datetime
|
|
example:
|
|
id: "550e8400-e29b-41d4-a716-446655440000"
|
|
email: "user@example.com"
|
|
name: "John Doe"
|
|
created_at: "2024-12-19T06:00:00Z"
|
|
- status: 401
|
|
description: "Not authenticated"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "Not authenticated"
|
|
depends_on_models: [model_user]
|
|
depends_on_apis: []
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
|
|
# ========================================
|
|
# RECORDING ENDPOINTS
|
|
# ========================================
|
|
- id: api_list_recordings
|
|
method: GET
|
|
path: /api/recordings
|
|
summary: "List user recordings"
|
|
description: "Get all recordings for authenticated user, sorted by creation date"
|
|
tags: [recordings]
|
|
path_params: []
|
|
query_params:
|
|
- name: limit
|
|
type: integer
|
|
required: false
|
|
default: 50
|
|
description: "Maximum number of recordings to return"
|
|
- name: offset
|
|
type: integer
|
|
required: false
|
|
default: 0
|
|
description: "Number of recordings to skip"
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties: []
|
|
responses:
|
|
- status: 200
|
|
description: "Recordings list"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: recordings
|
|
type: array
|
|
- name: total
|
|
type: integer
|
|
example:
|
|
recordings:
|
|
- id: "rec-123"
|
|
title: "Meeting Notes"
|
|
duration: 180
|
|
created_at: "2024-12-19T06:00:00Z"
|
|
total: 42
|
|
depends_on_models: [model_recording]
|
|
depends_on_apis: []
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
|
|
- id: api_create_recording
|
|
method: POST
|
|
path: /api/recordings
|
|
summary: "Create new recording"
|
|
description: "Upload audio file and create recording metadata"
|
|
tags: [recordings]
|
|
path_params: []
|
|
query_params: []
|
|
request_body:
|
|
content_type: multipart/form-data
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: audio
|
|
type: file
|
|
required: true
|
|
description: "Audio file (webm, mp3, wav)"
|
|
- name: title
|
|
type: string
|
|
required: false
|
|
description: "Recording title (auto-generated if not provided)"
|
|
- name: duration
|
|
type: integer
|
|
required: true
|
|
description: "Duration in seconds"
|
|
responses:
|
|
- status: 201
|
|
description: "Recording created"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: id
|
|
type: uuid
|
|
- name: title
|
|
type: string
|
|
- name: audio_file_path
|
|
type: string
|
|
- name: duration
|
|
type: integer
|
|
- name: created_at
|
|
type: datetime
|
|
example:
|
|
id: "rec-123"
|
|
title: "Recording 2024-12-19"
|
|
audio_file_path: "recordings/user-123/rec-123.webm"
|
|
duration: 180
|
|
created_at: "2024-12-19T06:00:00Z"
|
|
- status: 400
|
|
description: "Invalid audio file"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "Invalid audio format"
|
|
depends_on_models: [model_recording]
|
|
depends_on_apis: []
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
|
|
- id: api_get_recording
|
|
method: GET
|
|
path: /api/recordings/[id]
|
|
summary: "Get single recording"
|
|
description: "Get recording details including transcript and summary"
|
|
tags: [recordings]
|
|
path_params:
|
|
- name: id
|
|
type: uuid
|
|
description: "Recording ID"
|
|
query_params: []
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties: []
|
|
responses:
|
|
- status: 200
|
|
description: "Recording details"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: id
|
|
type: uuid
|
|
- name: title
|
|
type: string
|
|
- name: audio_file_path
|
|
type: string
|
|
- name: duration
|
|
type: integer
|
|
- name: transcript
|
|
type: string
|
|
- name: summary
|
|
type: string
|
|
- name: is_transcribing
|
|
type: boolean
|
|
- name: created_at
|
|
type: datetime
|
|
- status: 404
|
|
description: "Recording not found"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "Recording not found"
|
|
depends_on_models: [model_recording]
|
|
depends_on_apis: []
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
|
|
- id: api_delete_recording
|
|
method: DELETE
|
|
path: /api/recordings/[id]
|
|
summary: "Delete recording"
|
|
description: "Delete recording and associated audio file"
|
|
tags: [recordings]
|
|
path_params:
|
|
- name: id
|
|
type: uuid
|
|
description: "Recording ID"
|
|
query_params: []
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties: []
|
|
responses:
|
|
- status: 200
|
|
description: "Recording deleted"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: success
|
|
type: boolean
|
|
example:
|
|
success: true
|
|
- status: 404
|
|
description: "Recording not found"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "Recording not found"
|
|
depends_on_models: [model_recording]
|
|
depends_on_apis: []
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
|
|
- id: api_transcribe_recording
|
|
method: POST
|
|
path: /api/recordings/[id]/transcribe
|
|
summary: "Transcribe recording"
|
|
description: "Run Whisper STT on audio file to generate transcript"
|
|
tags: [recordings, ai]
|
|
path_params:
|
|
- name: id
|
|
type: uuid
|
|
description: "Recording ID"
|
|
query_params: []
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties: []
|
|
responses:
|
|
- status: 200
|
|
description: "Transcription started"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: recording_id
|
|
type: uuid
|
|
- name: is_transcribing
|
|
type: boolean
|
|
example:
|
|
recording_id: "rec-123"
|
|
is_transcribing: true
|
|
- status: 404
|
|
description: "Recording not found"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "Recording not found"
|
|
depends_on_models: [model_recording]
|
|
depends_on_apis: []
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
|
|
- id: api_summarize_recording
|
|
method: POST
|
|
path: /api/recordings/[id]/summarize
|
|
summary: "Summarize recording"
|
|
description: "Use Gemini to generate summary from transcript"
|
|
tags: [recordings, ai]
|
|
path_params:
|
|
- name: id
|
|
type: uuid
|
|
description: "Recording ID"
|
|
query_params: []
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties: []
|
|
responses:
|
|
- status: 200
|
|
description: "Summary generated"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: recording_id
|
|
type: uuid
|
|
- name: summary
|
|
type: string
|
|
example:
|
|
recording_id: "rec-123"
|
|
summary: "Meeting discussion about Q1 goals..."
|
|
- status: 400
|
|
description: "No transcript available"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "Transcript not available"
|
|
- status: 404
|
|
description: "Recording not found"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "Recording not found"
|
|
depends_on_models: [model_recording]
|
|
depends_on_apis: [api_transcribe_recording]
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
|
|
# ========================================
|
|
# GENERATED APP ENDPOINTS
|
|
# ========================================
|
|
- id: api_list_apps
|
|
method: GET
|
|
path: /api/apps
|
|
summary: "List generated apps"
|
|
description: "Get all apps generated for authenticated user"
|
|
tags: [apps]
|
|
path_params: []
|
|
query_params:
|
|
- name: limit
|
|
type: integer
|
|
required: false
|
|
default: 50
|
|
description: "Maximum number of apps to return"
|
|
- name: offset
|
|
type: integer
|
|
required: false
|
|
default: 0
|
|
description: "Number of apps to skip"
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties: []
|
|
responses:
|
|
- status: 200
|
|
description: "Apps list"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: apps
|
|
type: array
|
|
- name: total
|
|
type: integer
|
|
example:
|
|
apps:
|
|
- id: "app-123"
|
|
title: "Todo App"
|
|
app_type: "todo"
|
|
status: "completed"
|
|
created_at: "2024-12-19T06:00:00Z"
|
|
total: 12
|
|
depends_on_models: [model_generated_app]
|
|
depends_on_apis: []
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
|
|
- id: api_generate_app
|
|
method: POST
|
|
path: /api/apps/generate
|
|
summary: "Generate app from recording"
|
|
description: "Trigger AI app generation based on recording summary (wake word detection)"
|
|
tags: [apps, ai]
|
|
path_params: []
|
|
query_params: []
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: recording_id
|
|
type: uuid
|
|
required: true
|
|
description: "Source recording ID"
|
|
example:
|
|
recording_id: "rec-123"
|
|
responses:
|
|
- status: 201
|
|
description: "App generation started"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: id
|
|
type: uuid
|
|
- name: recording_id
|
|
type: uuid
|
|
- name: status
|
|
type: string
|
|
example:
|
|
id: "app-123"
|
|
recording_id: "rec-123"
|
|
status: "generating"
|
|
- status: 400
|
|
description: "Recording has no summary"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "Recording summary not available"
|
|
- status: 404
|
|
description: "Recording not found"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "Recording not found"
|
|
depends_on_models: [model_generated_app, model_recording]
|
|
depends_on_apis: [api_summarize_recording]
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
|
|
- id: api_get_app
|
|
method: GET
|
|
path: /api/apps/[id]
|
|
summary: "Get single generated app"
|
|
description: "Get app details including HTML content for iframe rendering"
|
|
tags: [apps]
|
|
path_params:
|
|
- name: id
|
|
type: uuid
|
|
description: "App ID"
|
|
query_params: []
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties: []
|
|
responses:
|
|
- status: 200
|
|
description: "App details"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: id
|
|
type: uuid
|
|
- name: recording_id
|
|
type: uuid
|
|
- name: title
|
|
type: string
|
|
- name: description
|
|
type: string
|
|
- name: html_content
|
|
type: string
|
|
- name: app_type
|
|
type: string
|
|
- name: status
|
|
type: string
|
|
- name: created_at
|
|
type: datetime
|
|
- status: 404
|
|
description: "App not found"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "App not found"
|
|
depends_on_models: [model_generated_app]
|
|
depends_on_apis: []
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
|
|
- id: api_delete_app
|
|
method: DELETE
|
|
path: /api/apps/[id]
|
|
summary: "Delete generated app"
|
|
description: "Delete generated app (recording remains)"
|
|
tags: [apps]
|
|
path_params:
|
|
- name: id
|
|
type: uuid
|
|
description: "App ID"
|
|
query_params: []
|
|
request_body:
|
|
content_type: application/json
|
|
schema:
|
|
type: object
|
|
properties: []
|
|
responses:
|
|
- status: 200
|
|
description: "App deleted"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: success
|
|
type: boolean
|
|
example:
|
|
success: true
|
|
- status: 404
|
|
description: "App not found"
|
|
schema:
|
|
type: object
|
|
properties:
|
|
- name: error
|
|
type: string
|
|
example:
|
|
error: "App not found"
|
|
depends_on_models: [model_generated_app]
|
|
depends_on_apis: []
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
|
|
# ============================================================================
|
|
# LAYER 3: PAGES
|
|
# ============================================================================
|
|
pages:
|
|
- id: page_home
|
|
name: "Home"
|
|
path: /
|
|
layout: layout_public
|
|
data_needs: []
|
|
components: [component_header, component_hero, component_features]
|
|
seo:
|
|
title: "NoteToApp - Voice Recording with AI Summarization"
|
|
description: "Record voice notes, get AI summaries, and generate apps automatically"
|
|
auth:
|
|
required: false
|
|
roles: []
|
|
redirect: ""
|
|
state:
|
|
local: []
|
|
global: []
|
|
|
|
- id: page_login
|
|
name: "Login"
|
|
path: /login
|
|
layout: layout_auth
|
|
data_needs: []
|
|
components: [component_login_form]
|
|
seo:
|
|
title: "Login - NoteToApp"
|
|
description: "Login to your account"
|
|
auth:
|
|
required: false
|
|
roles: []
|
|
redirect: ""
|
|
state:
|
|
local: [error, isLoading]
|
|
global: []
|
|
|
|
- id: page_register
|
|
name: "Register"
|
|
path: /register
|
|
layout: layout_auth
|
|
data_needs: []
|
|
components: [component_register_form]
|
|
seo:
|
|
title: "Register - NoteToApp"
|
|
description: "Create a new account"
|
|
auth:
|
|
required: false
|
|
roles: []
|
|
redirect: ""
|
|
state:
|
|
local: [error, isLoading]
|
|
global: []
|
|
|
|
- id: page_dashboard
|
|
name: "Dashboard"
|
|
path: /dashboard
|
|
layout: layout_app
|
|
data_needs:
|
|
- api_id: api_get_current_user
|
|
purpose: "Display user info"
|
|
on_load: true
|
|
- api_id: api_list_recordings
|
|
purpose: "Show recent recordings"
|
|
on_load: true
|
|
components: [component_header, component_sidebar, component_record_button, component_wake_word_indicator, component_recording_list]
|
|
seo:
|
|
title: "Dashboard - NoteToApp"
|
|
description: "Your voice recordings and generated apps"
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
redirect: /login
|
|
state:
|
|
local: [isRecording, isListening, currentRecording]
|
|
global: [user]
|
|
|
|
- id: page_recordings
|
|
name: "Recordings"
|
|
path: /recordings
|
|
layout: layout_app
|
|
data_needs:
|
|
- api_id: api_list_recordings
|
|
purpose: "Display all recordings"
|
|
on_load: true
|
|
components: [component_header, component_sidebar, component_recording_list, component_recording_card]
|
|
seo:
|
|
title: "Recordings - NoteToApp"
|
|
description: "All your voice recordings"
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
redirect: /login
|
|
state:
|
|
local: [searchQuery, sortBy]
|
|
global: [user]
|
|
|
|
- id: page_recording_detail
|
|
name: "Recording Detail"
|
|
path: /recordings/[id]
|
|
layout: layout_app
|
|
data_needs:
|
|
- api_id: api_get_recording
|
|
purpose: "Display recording details"
|
|
on_load: true
|
|
components: [component_header, component_sidebar, component_audio_player, component_transcript_viewer, component_summary_display]
|
|
seo:
|
|
title: "Recording Detail - NoteToApp"
|
|
description: "View recording transcript and summary"
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
redirect: /login
|
|
state:
|
|
local: [isPlaying, currentTime, isTranscribing]
|
|
global: [user]
|
|
|
|
- id: page_apps
|
|
name: "Generated Apps"
|
|
path: /apps
|
|
layout: layout_app
|
|
data_needs:
|
|
- api_id: api_list_apps
|
|
purpose: "Display generated apps gallery"
|
|
on_load: true
|
|
components: [component_header, component_sidebar, component_app_gallery, component_app_card]
|
|
seo:
|
|
title: "Generated Apps - NoteToApp"
|
|
description: "Your AI-generated applications"
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
redirect: /login
|
|
state:
|
|
local: [filterType]
|
|
global: [user]
|
|
|
|
- id: page_app_detail
|
|
name: "App Preview"
|
|
path: /apps/[id]
|
|
layout: layout_app
|
|
data_needs:
|
|
- api_id: api_get_app
|
|
purpose: "Display app in iframe"
|
|
on_load: true
|
|
components: [component_header, component_sidebar, component_app_iframe_viewer]
|
|
seo:
|
|
title: "App Preview - NoteToApp"
|
|
description: "View generated application"
|
|
auth:
|
|
required: true
|
|
roles: []
|
|
redirect: /login
|
|
state:
|
|
local: [isLoading]
|
|
global: [user]
|
|
|
|
# ============================================================================
|
|
# LAYER 3: COMPONENTS
|
|
# ============================================================================
|
|
components:
|
|
# ========================================
|
|
# LAYOUT COMPONENTS
|
|
# ========================================
|
|
- id: component_header
|
|
name: Header
|
|
props:
|
|
- name: user
|
|
type: User | null
|
|
required: false
|
|
default: null
|
|
description: "Current user for displaying name/avatar"
|
|
events: []
|
|
uses_apis: []
|
|
uses_components: []
|
|
internal_state: []
|
|
variants: [default]
|
|
|
|
- id: component_sidebar
|
|
name: Sidebar
|
|
props:
|
|
- name: activePath
|
|
type: string
|
|
required: true
|
|
description: "Current route path for highlighting"
|
|
events:
|
|
- name: onNavigate
|
|
payload: "string"
|
|
description: "Fired when navigation item clicked"
|
|
uses_apis: []
|
|
uses_components: []
|
|
internal_state: [isCollapsed]
|
|
variants: [default]
|
|
|
|
- id: component_hero
|
|
name: Hero
|
|
props: []
|
|
events:
|
|
- name: onGetStarted
|
|
payload: "void"
|
|
description: "Fired when CTA button clicked"
|
|
uses_apis: []
|
|
uses_components: []
|
|
internal_state: []
|
|
variants: [default]
|
|
|
|
- id: component_features
|
|
name: Features
|
|
props: []
|
|
events: []
|
|
uses_apis: []
|
|
uses_components: []
|
|
internal_state: []
|
|
variants: [default]
|
|
|
|
# ========================================
|
|
# AUTHENTICATION COMPONENTS
|
|
# ========================================
|
|
- id: component_login_form
|
|
name: LoginForm
|
|
props: []
|
|
events:
|
|
- name: onSuccess
|
|
payload: "User"
|
|
description: "Fired when login successful"
|
|
- name: onError
|
|
payload: "string"
|
|
description: "Fired when login fails"
|
|
uses_apis: [api_login_user]
|
|
uses_components: []
|
|
internal_state: [email, password, isLoading, error]
|
|
variants: [default]
|
|
|
|
- id: component_register_form
|
|
name: RegisterForm
|
|
props: []
|
|
events:
|
|
- name: onSuccess
|
|
payload: "User"
|
|
description: "Fired when registration successful"
|
|
- name: onError
|
|
payload: "string"
|
|
description: "Fired when registration fails"
|
|
uses_apis: [api_register_user]
|
|
uses_components: []
|
|
internal_state: [email, name, password, confirmPassword, isLoading, error]
|
|
variants: [default]
|
|
|
|
# ========================================
|
|
# RECORDING COMPONENTS
|
|
# ========================================
|
|
- id: component_record_button
|
|
name: RecordButton
|
|
props:
|
|
- name: isRecording
|
|
type: boolean
|
|
required: true
|
|
description: "Whether currently recording"
|
|
- name: isTranscribing
|
|
type: boolean
|
|
required: false
|
|
default: false
|
|
description: "Whether transcribing in real-time"
|
|
events:
|
|
- name: onStartRecording
|
|
payload: "void"
|
|
description: "Fired when recording starts"
|
|
- name: onStopRecording
|
|
payload: "Blob"
|
|
description: "Fired when recording stops, includes audio blob"
|
|
uses_apis: []
|
|
uses_components: []
|
|
internal_state: [mediaRecorder, audioChunks]
|
|
variants: [default, large]
|
|
|
|
- id: component_wake_word_indicator
|
|
name: WakeWordIndicator
|
|
props:
|
|
- name: isListening
|
|
type: boolean
|
|
required: true
|
|
description: "Whether wake word detection is active"
|
|
- name: wakeWord
|
|
type: string
|
|
required: true
|
|
description: "The wake word phrase"
|
|
events:
|
|
- name: onWakeWordDetected
|
|
payload: "void"
|
|
description: "Fired when wake word is detected"
|
|
uses_apis: []
|
|
uses_components: []
|
|
internal_state: [lastDetectionTime]
|
|
variants: [default]
|
|
|
|
- id: component_recording_list
|
|
name: RecordingList
|
|
props:
|
|
- name: recordings
|
|
type: "Recording[]"
|
|
required: true
|
|
description: "Array of recordings to display"
|
|
- name: isLoading
|
|
type: boolean
|
|
required: false
|
|
default: false
|
|
description: "Whether recordings are being loaded"
|
|
events:
|
|
- name: onSelectRecording
|
|
payload: "string"
|
|
description: "Fired when recording is clicked, payload is recording ID"
|
|
- name: onDeleteRecording
|
|
payload: "string"
|
|
description: "Fired when delete confirmed, payload is recording ID"
|
|
uses_apis: []
|
|
uses_components: [component_recording_card]
|
|
internal_state: []
|
|
variants: [default, compact]
|
|
|
|
- id: component_recording_card
|
|
name: RecordingCard
|
|
props:
|
|
- name: recording
|
|
type: Recording
|
|
required: true
|
|
description: "Recording to display"
|
|
- name: showActions
|
|
type: boolean
|
|
required: false
|
|
default: true
|
|
description: "Show edit/delete buttons"
|
|
events:
|
|
- name: onClick
|
|
payload: "string"
|
|
description: "Fired when card clicked, payload is recording ID"
|
|
- name: onDelete
|
|
payload: "string"
|
|
description: "Fired when delete confirmed, payload is recording ID"
|
|
uses_apis: []
|
|
uses_components: []
|
|
internal_state: [isDeleting]
|
|
variants: [default, compact]
|
|
|
|
- id: component_audio_player
|
|
name: AudioPlayer
|
|
props:
|
|
- name: audioUrl
|
|
type: string
|
|
required: true
|
|
description: "URL to audio file"
|
|
- name: duration
|
|
type: integer
|
|
required: true
|
|
description: "Duration in seconds"
|
|
events:
|
|
- name: onPlayPause
|
|
payload: "boolean"
|
|
description: "Fired when play/pause toggled, payload is isPlaying"
|
|
- name: onSeek
|
|
payload: "number"
|
|
description: "Fired when user seeks, payload is new time in seconds"
|
|
uses_apis: []
|
|
uses_components: []
|
|
internal_state: [isPlaying, currentTime, audioElement]
|
|
variants: [default]
|
|
|
|
- id: component_transcript_viewer
|
|
name: TranscriptViewer
|
|
props:
|
|
- name: transcript
|
|
type: string
|
|
required: true
|
|
description: "Transcript text to display"
|
|
- name: isLive
|
|
type: boolean
|
|
required: false
|
|
default: false
|
|
description: "Whether showing live transcription"
|
|
events: []
|
|
uses_apis: []
|
|
uses_components: []
|
|
internal_state: []
|
|
variants: [default, live]
|
|
|
|
- id: component_summary_display
|
|
name: SummaryDisplay
|
|
props:
|
|
- name: summary
|
|
type: string
|
|
required: true
|
|
description: "AI-generated summary text"
|
|
- name: isLoading
|
|
type: boolean
|
|
required: false
|
|
default: false
|
|
description: "Whether summary is being generated"
|
|
events: []
|
|
uses_apis: []
|
|
uses_components: []
|
|
internal_state: []
|
|
variants: [default]
|
|
|
|
# ========================================
|
|
# GENERATED APP COMPONENTS
|
|
# ========================================
|
|
- id: component_app_gallery
|
|
name: AppGallery
|
|
props:
|
|
- name: apps
|
|
type: "GeneratedApp[]"
|
|
required: true
|
|
description: "Array of generated apps to display"
|
|
- name: isLoading
|
|
type: boolean
|
|
required: false
|
|
default: false
|
|
description: "Whether apps are being loaded"
|
|
events:
|
|
- name: onSelectApp
|
|
payload: "string"
|
|
description: "Fired when app is clicked, payload is app ID"
|
|
uses_apis: []
|
|
uses_components: [component_app_card]
|
|
internal_state: []
|
|
variants: [grid, list]
|
|
|
|
- id: component_app_card
|
|
name: AppCard
|
|
props:
|
|
- name: app
|
|
type: GeneratedApp
|
|
required: true
|
|
description: "Generated app to display"
|
|
- name: showActions
|
|
type: boolean
|
|
required: false
|
|
default: true
|
|
description: "Show delete button"
|
|
events:
|
|
- name: onClick
|
|
payload: "string"
|
|
description: "Fired when card clicked, payload is app ID"
|
|
- name: onDelete
|
|
payload: "string"
|
|
description: "Fired when delete confirmed, payload is app ID"
|
|
uses_apis: []
|
|
uses_components: []
|
|
internal_state: [isDeleting]
|
|
variants: [default, compact]
|
|
|
|
- id: component_app_iframe_viewer
|
|
name: AppIframeViewer
|
|
props:
|
|
- name: htmlContent
|
|
type: string
|
|
required: true
|
|
description: "HTML content to render in iframe"
|
|
- name: title
|
|
type: string
|
|
required: true
|
|
description: "App title for accessibility"
|
|
events:
|
|
- name: onLoadComplete
|
|
payload: "void"
|
|
description: "Fired when iframe finishes loading"
|
|
uses_apis: []
|
|
uses_components: []
|
|
internal_state: [isLoading, iframeRef]
|
|
variants: [default, fullscreen]
|
|
|
|
# ============================================================================
|
|
# DEPENDENCY GRAPH
|
|
# ============================================================================
|
|
dependency_graph:
|
|
layers:
|
|
- layer: 1
|
|
name: "Data Models"
|
|
description: "Database schema - no dependencies"
|
|
items:
|
|
- id: model_user
|
|
type: model
|
|
dependencies: []
|
|
- id: model_recording
|
|
type: model
|
|
dependencies: []
|
|
- id: model_generated_app
|
|
type: model
|
|
dependencies: []
|
|
|
|
- layer: 2
|
|
name: "API Endpoints"
|
|
description: "Backend APIs - depend on models"
|
|
items:
|
|
- id: api_register_user
|
|
type: api
|
|
dependencies: [model_user]
|
|
- id: api_login_user
|
|
type: api
|
|
dependencies: [model_user]
|
|
- id: api_logout_user
|
|
type: api
|
|
dependencies: []
|
|
- id: api_get_current_user
|
|
type: api
|
|
dependencies: [model_user]
|
|
- id: api_list_recordings
|
|
type: api
|
|
dependencies: [model_recording]
|
|
- id: api_create_recording
|
|
type: api
|
|
dependencies: [model_recording]
|
|
- id: api_get_recording
|
|
type: api
|
|
dependencies: [model_recording]
|
|
- id: api_delete_recording
|
|
type: api
|
|
dependencies: [model_recording]
|
|
- id: api_transcribe_recording
|
|
type: api
|
|
dependencies: [model_recording]
|
|
- id: api_summarize_recording
|
|
type: api
|
|
dependencies: [model_recording]
|
|
- id: api_list_apps
|
|
type: api
|
|
dependencies: [model_generated_app]
|
|
- id: api_generate_app
|
|
type: api
|
|
dependencies: [model_generated_app, model_recording]
|
|
- id: api_get_app
|
|
type: api
|
|
dependencies: [model_generated_app]
|
|
- id: api_delete_app
|
|
type: api
|
|
dependencies: [model_generated_app]
|
|
|
|
- layer: 3
|
|
name: "UI Components & Pages"
|
|
description: "Frontend - depend on APIs"
|
|
items:
|
|
# Components
|
|
- id: component_header
|
|
type: component
|
|
dependencies: []
|
|
- id: component_sidebar
|
|
type: component
|
|
dependencies: []
|
|
- id: component_hero
|
|
type: component
|
|
dependencies: []
|
|
- id: component_features
|
|
type: component
|
|
dependencies: []
|
|
- id: component_login_form
|
|
type: component
|
|
dependencies: [api_login_user]
|
|
- id: component_register_form
|
|
type: component
|
|
dependencies: [api_register_user]
|
|
- id: component_record_button
|
|
type: component
|
|
dependencies: []
|
|
- id: component_wake_word_indicator
|
|
type: component
|
|
dependencies: []
|
|
- id: component_recording_list
|
|
type: component
|
|
dependencies: [component_recording_card]
|
|
- id: component_recording_card
|
|
type: component
|
|
dependencies: []
|
|
- id: component_audio_player
|
|
type: component
|
|
dependencies: []
|
|
- id: component_transcript_viewer
|
|
type: component
|
|
dependencies: []
|
|
- id: component_summary_display
|
|
type: component
|
|
dependencies: []
|
|
- id: component_app_gallery
|
|
type: component
|
|
dependencies: [component_app_card]
|
|
- id: component_app_card
|
|
type: component
|
|
dependencies: []
|
|
- id: component_app_iframe_viewer
|
|
type: component
|
|
dependencies: []
|
|
|
|
# Pages
|
|
- id: page_home
|
|
type: page
|
|
dependencies: [component_header, component_hero, component_features]
|
|
- id: page_login
|
|
type: page
|
|
dependencies: [component_login_form]
|
|
- id: page_register
|
|
type: page
|
|
dependencies: [component_register_form]
|
|
- id: page_dashboard
|
|
type: page
|
|
dependencies: [api_get_current_user, api_list_recordings, component_header, component_sidebar, component_record_button, component_wake_word_indicator, component_recording_list]
|
|
- id: page_recordings
|
|
type: page
|
|
dependencies: [api_list_recordings, component_header, component_sidebar, component_recording_list, component_recording_card]
|
|
- id: page_recording_detail
|
|
type: page
|
|
dependencies: [api_get_recording, component_header, component_sidebar, component_audio_player, component_transcript_viewer, component_summary_display]
|
|
- id: page_apps
|
|
type: page
|
|
dependencies: [api_list_apps, component_header, component_sidebar, component_app_gallery, component_app_card]
|
|
- id: page_app_detail
|
|
type: page
|
|
dependencies: [api_get_app, component_header, component_sidebar, component_app_iframe_viewer]
|
|
|
|
dependency_map:
|
|
# Models
|
|
model_user:
|
|
depends_on: []
|
|
depended_by: [api_register_user, api_login_user, api_get_current_user]
|
|
model_recording:
|
|
depends_on: []
|
|
depended_by: [api_list_recordings, api_create_recording, api_get_recording, api_delete_recording, api_transcribe_recording, api_summarize_recording, api_generate_app]
|
|
model_generated_app:
|
|
depends_on: []
|
|
depended_by: [api_list_apps, api_generate_app, api_get_app, api_delete_app]
|
|
|
|
# APIs - Auth
|
|
api_register_user:
|
|
depends_on: [model_user]
|
|
depended_by: [component_register_form]
|
|
api_login_user:
|
|
depends_on: [model_user]
|
|
depended_by: [component_login_form]
|
|
api_logout_user:
|
|
depends_on: []
|
|
depended_by: []
|
|
api_get_current_user:
|
|
depends_on: [model_user]
|
|
depended_by: [page_dashboard]
|
|
|
|
# APIs - Recordings
|
|
api_list_recordings:
|
|
depends_on: [model_recording]
|
|
depended_by: [page_dashboard, page_recordings]
|
|
api_create_recording:
|
|
depends_on: [model_recording]
|
|
depended_by: []
|
|
api_get_recording:
|
|
depends_on: [model_recording]
|
|
depended_by: [page_recording_detail]
|
|
api_delete_recording:
|
|
depends_on: [model_recording]
|
|
depended_by: []
|
|
api_transcribe_recording:
|
|
depends_on: [model_recording]
|
|
depended_by: []
|
|
api_summarize_recording:
|
|
depends_on: [model_recording]
|
|
depended_by: []
|
|
|
|
# APIs - Apps
|
|
api_list_apps:
|
|
depends_on: [model_generated_app]
|
|
depended_by: [page_apps]
|
|
api_generate_app:
|
|
depends_on: [model_generated_app, model_recording]
|
|
depended_by: []
|
|
api_get_app:
|
|
depends_on: [model_generated_app]
|
|
depended_by: [page_app_detail]
|
|
api_delete_app:
|
|
depends_on: [model_generated_app]
|
|
depended_by: []
|
|
|
|
# Components
|
|
component_header:
|
|
depends_on: []
|
|
depended_by: [page_home, page_dashboard, page_recordings, page_recording_detail, page_apps, page_app_detail]
|
|
component_sidebar:
|
|
depends_on: []
|
|
depended_by: [page_dashboard, page_recordings, page_recording_detail, page_apps, page_app_detail]
|
|
component_hero:
|
|
depends_on: []
|
|
depended_by: [page_home]
|
|
component_features:
|
|
depends_on: []
|
|
depended_by: [page_home]
|
|
component_login_form:
|
|
depends_on: [api_login_user]
|
|
depended_by: [page_login]
|
|
component_register_form:
|
|
depends_on: [api_register_user]
|
|
depended_by: [page_register]
|
|
component_record_button:
|
|
depends_on: []
|
|
depended_by: [page_dashboard]
|
|
component_wake_word_indicator:
|
|
depends_on: []
|
|
depended_by: [page_dashboard]
|
|
component_recording_list:
|
|
depends_on: [component_recording_card]
|
|
depended_by: [page_dashboard, page_recordings]
|
|
component_recording_card:
|
|
depends_on: []
|
|
depended_by: [component_recording_list, page_recordings]
|
|
component_audio_player:
|
|
depends_on: []
|
|
depended_by: [page_recording_detail]
|
|
component_transcript_viewer:
|
|
depends_on: []
|
|
depended_by: [page_recording_detail]
|
|
component_summary_display:
|
|
depends_on: []
|
|
depended_by: [page_recording_detail]
|
|
component_app_gallery:
|
|
depends_on: [component_app_card]
|
|
depended_by: [page_apps]
|
|
component_app_card:
|
|
depends_on: []
|
|
depended_by: [component_app_gallery, page_apps]
|
|
component_app_iframe_viewer:
|
|
depends_on: []
|
|
depended_by: [page_app_detail]
|
|
|
|
# Pages
|
|
page_home:
|
|
depends_on: [component_header, component_hero, component_features]
|
|
depended_by: []
|
|
page_login:
|
|
depends_on: [component_login_form]
|
|
depended_by: []
|
|
page_register:
|
|
depends_on: [component_register_form]
|
|
depended_by: []
|
|
page_dashboard:
|
|
depends_on: [api_get_current_user, api_list_recordings, component_header, component_sidebar, component_record_button, component_wake_word_indicator, component_recording_list]
|
|
depended_by: []
|
|
page_recordings:
|
|
depends_on: [api_list_recordings, component_header, component_sidebar, component_recording_list, component_recording_card]
|
|
depended_by: []
|
|
page_recording_detail:
|
|
depends_on: [api_get_recording, component_header, component_sidebar, component_audio_player, component_transcript_viewer, component_summary_display]
|
|
depended_by: []
|
|
page_apps:
|
|
depends_on: [api_list_apps, component_header, component_sidebar, component_app_gallery, component_app_card]
|
|
depended_by: []
|
|
page_app_detail:
|
|
depends_on: [api_get_app, component_header, component_sidebar, component_app_iframe_viewer]
|
|
depended_by: []
|