Proposed implementation plan to make reusable speech discoverable across categories and generation workflows without breaking the existing Soundboard.
Soundboard is already SpeakTrue’s durable audio store, but its primary navigation model is “remember the folder.” A Unified Speech Library should add global retrieval—recent, search, favorites, tags, and source filters—while keeping categories, ordering, offline behavior, and existing save contracts intact.
A signed-in user can search all of their clips, filter to favorites or a generation source, play a result, see where it lives, and reuse it without first navigating to its category.
public.soundboard_clips already stores owner, category, file name, stable storage path, transcript, generation metadata, duration, order, offline availability, and timestamps.SoundboardDtos.kt; iOS CloudClipRecord does not yet select or decode metadata.supabase_soundboard_categories_service.py and folder-oriented routes.metadata JSON;Add Library as a retrieval view, not a second storage system.
soundboard_clips additionsis_favorite boolean not null default false(user_id, is_favorite, updated_at desc) where favorite is true(user_id, created_at desc) for recentsDo not put favorite state in metadata; metadata remains immutable generation provenance.
soundboard_tags| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
user_id |
uuid | Owner |
name |
text | Display name, bounded and trimmed |
normalized_name |
text | Unique per owner, case-insensitive |
color_token |
text nullable | Controlled design token, not raw CSS |
| timestamps | timestamptz | UTC |
soundboard_clip_tagsclip_id, tag_id, and user_id;(clip_id, tag_id);Start with indexed owner/category/favorite/source filters and bounded case-insensitive matching over file_name and transcript. Measure scale and query plans before adding generated full-text vectors. Do not add semantic search until content-processing privacy, cost, and relevance are separately approved.
Add soundboard-library-query as the normalized cross-platform read boundary.
Request:
{
"query": "appointment",
"favorite_only": false,
"category_ids": [],
"tag_ids": [],
"sources": ["tts", "sts-live-interpreter"],
"offline_only": false,
"cursor": null,
"limit": 30
}
Response:
{
"items": [
{
"id": "uuid",
"category": { "id": "uuid", "name": "Saved TTS" },
"file_name": "Appointment reminder.mp3",
"storage_path": "users/.../soundboard/...",
"transcript": "Appointment reminder",
"metadata": { "source": "tts" },
"duration_ms": 1800,
"is_offline_available": true,
"is_favorite": true,
"tags": [],
"created_at": "2026-07-14T00:00:00Z"
}
],
"next_cursor": null
}
The query response does not include a signed audio URL by default. Add soundboard-library-resolve or reuse an owner-scoped existing resolver to return a short-lived URL for one selected item.
Mutations:
soundboard-library-favorite with {clip_id, is_favorite};soundboard-tag-create, soundboard-tag-update, and soundboard-tag-delete;soundboard-clip-tags-update with the complete desired owned tag ID set.All mutations are idempotent and return the resulting resource state. Direct-table compatibility may remain temporarily, but new clients should converge on the normalized contract.
backend/supabase/migrations/, then refresh 001_core_schema.sql after validation.backend/supabase/functions/soundboard-library-*/ and tag handler tests.backend/supabase/functions/soundboard-save-generated/ response compatibility.web/python-web-app/static/js/index_soundboard.js and the current Soundboard partial/template.web/python-web-app/src/routes/soundboard.py and service boundaries under web/python-web-app/src/services/.test_soundboard_main_tab_surface.py, strict/Supabase service tests, and a focused JS runtime check.CloudClipRecord and Soundboard models in ios/SpeakTrue/CloudStorageService.swift and ios/SpeakTrue/SoundboardAudioClips.swift.ios/SpeakTrue/SoundboardViewModel.swift; avoid duplicating cache/player logic.ios/SpeakTrue/SoundboardView.swift or a dedicated view linked from the same navigation shell.SoundboardDtos.kt, mappers, and SoundboardRepository.kt.SoundboardViewModel.kt and UI to SoundboardScreen.kt or a dedicated nested route.L01: Ship global recent and search on web — Risk: high; Depends: query limits and search semantics decision; Mode: HITL.
Add the read-only query edge function, owner-scoped pagination, no-URL result envelope, web Library view, play-time URL resolution, and focused query/UI tests. No schema mutation beyond indexes.
After this: a web user can find and play any owned clip without opening its category.
L02: Add favorite state end to end on web — Risk: medium; Depends: L01; Mode: AFK.
Add the column/index, idempotent mutation, result field, optimistic web control with rollback, old-client fixture, and migration regression coverage.
After this: a web user can favorite a clip and filter the library to favorites.
L03: Add user-defined tags on web — Risk: high; Depends: L01; Mode: HITL.
Add tag tables/RLS/ownership checks, tag CRUD and clip association contracts, web assignment/filter UI, duplicate-name handling, and delete-cascade tests.
After this: a web user can organize clips across categories without moving them.
L04: Add iOS library search and favorites — Risk: medium; Depends: L01-L02; Mode: AFK.
Decode metadata/favorite/timestamps, add paginated search/recent/favorite state, reuse cache playback, handle expired URLs, and add focused coding/view-model tests.
After this: an iOS user has the same global retrieval and favorite behavior as web.
L05: Add Android library search and favorites — Risk: medium; Depends: L01-L02; Mode: AFK.
Extend DTOs/repository/state/UI, preserve storage-only combined clip handling, add focused tests, and run the complete Android change gate.
After this: an Android user has the same global retrieval and favorite behavior as web and iOS.
L06: Add native tag parity — Risk: medium; Depends: L03-L05; Mode: HITL.
Add native tag assignment/filtering with accessible controls, conflict refresh, and cross-device sync tests.
After this: tags created on any surface appear and filter correctly on the others.
L07: Connect generation provenance and conversations — Risk: medium; Depends: L01 and Conversation C08; Mode: AFK.
Normalize source filters from clip_metadata, ensure saved conversation turns retain provenance, and add a one-action path from turn to library result.
After this: a user can retrieve speech by how it was created and trace saved turns back to their workflow type.
L08: Harden cache, migration, and staged rollout — Risk: high; Depends: L04-L07; Mode: HITL.
Test pagination under mutation, URL expiry, offline cache preference, old clients, query plans, RLS, rollback, and allowlisted production smoke before enabling the Library by default.
After this: Unified Speech Library is releasable without weakening Soundboard compatibility.
bash scripts/verify_android_ci_local.sh before commit/push.git diff --check and accounts for git status --short.Disable speech_library_enabled and return clients to category-first Soundboard views. Keep additive columns/tables and dual-compatible decoding in place. Query and mutation functions can remain deployed but unreachable while investigation occurs. Do not remove favorite/tag data during rollback.