尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Repository for roots-and-echoes
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
A daily-use life-story companion for older adults — Story Time (timeline), Photos & Family (with a family tree), Music, Books, Art, and Games — built with large text, high contrast, and voice input throughout. See the full plan for the "why" behind these choices.檢視完整 README ↗A daily-use life-story companion for older adults — Story Time (timeline), Photos & Family (with a family tree), Music, Books, Art, and Games — built with large text, high contrast, and voice input throughout. See the full plan for the "why" behind these choices.
The app uses a bespoke, jewel-tone keepsake-book visual style called Warm Keepsake: serif display type (Playfair Display for headings, Fraunces for the wordmark's ampersand and italics) paired with the existing accessible sans-serif body text; a single universal rust accent (--color-primary) on every primary button and highlight; a hand-drawn line-icon system (src/components/Icon.tsx) replacing all emoji; a circular double-ring "medallion" icon badge (src/components/Medallion.tsx) marking which mode a piece of content belongs to; and a signature pure-CSS "corner-tick" ornament — four small bracket marks at the corners of every card, built entirely from layered CSS gradients (see .tile in src/app/globals.css) with no extra markup — applied consistently to every tile, list row, photo, and form throughout the app, including the family tree diagram and all four games. All of the app's original accessibility rules (20px+ base type with the A / A+ / A++ control, 56px+ touch targets, a warm high-contrast fixed-light theme, visible focus rings, read-aloud everywhere) are unchanged — this was a visual restyle only, layered on top of the same accessible foundation.
supabase/schema.sql, then
supabase/migration_2_placeholder_fields.sql, then supabase/storage.sql..env.example to .env.local and fill in the three values from your
Supabase project's Settings → API page (see comments in that file).npm install
npm run dev
Open http://localhost:3000 — you'll land on a real sign-in screen: enter an email address and click the link Supabase emails you (no password). The very first sign-in walks you through a one-time onboarding step (senior or caregiver, name, and — for a caregiver — who invited them). There's no seed data; every account starts empty and everything you add is really saved.
src/
app/ routes (Next.js App Router) — one folder per page
actions.ts all server actions (every write to data goes through here)
auth/callback/ finishes the magic-link sign-in (exchanges the emailed code
for a session)
onboarding/ first-time profile setup (senior vs. caregiver, name)
components/ shared UI, including the voice/audio capture widgets
components/games/ the four Games mode components (jigsaw, photo quiz, song quiz, art quiz)
lib/
types.ts the whole data model, typed
store.ts the real Supabase data layer — every function is an
async `supabase.from(...)` query, subject to the
row-level security policies in supabase/*.sql
storage.ts Supabase Storage uploads + signed playback URLs (song
audio, the custom voice sample)
session.ts resolves the signed-in Supabase Auth user + which
senior's story a caregiver is currently helping with
supabase/ browser & server Supabase clients
proxy.ts refreshes the Supabase session cookie on every request
supabase/
schema.sql the base Postgres schema + row-level security
migration_2_placeholder_fields.sql placeholder-tile columns + email-based
caregiver invites (run after schema.sql)
storage.sql the `audio`/`photos` Storage buckets + policies
This app is deployed against a real Supabase project — there is no mock data layer to swap out. What's actually needed to run it somewhere real:
supabase/ run once,
in order (see "Try it locally" above). Auth is Supabase's built-in
passwordless magic-link email — nothing else to configure.NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY — a publishable-type key, safe to expose to
the browser — and SITE_URL, the app's own deployed URL, used
to build the magic-link redirect) set both locally (.env.local) and in
the hosting provider's project settings.vercel from the CLI), with the same three environment variables
added in the Vercel dashboard.The Supabase secret/service-role key is never used anywhere in this app — every query runs as the signed-in user through Row Level Security, so the publishable key is all the running app ever needs.
Story Time's rotating prompts (PROMPTS_BY_CATEGORY in src/lib/store.ts) are a
curated, hand-written library — about a dozen original prompts per life-area
topic (Childhood, Family, Work, Travel, Music, Love, Milestones, Other),
matching the same MemoryCategory a saved memory is filed under — rather
than prompts fetched live from the web or generated on the fly by an AI
model at request time. That was a deliberate choice, not a shortcut:
The library is easy to grow later exactly like any other file in the repo —
add strings to the relevant array in PROMPTS_BY_CATEGORY and redeploy — so
if it starts feeling repetitive, extending it stays a small, low-risk change
rather than standing up a live content pipeline.
Song audio (via "Add a song" or the upload box on a song's page), the
custom voice sample (Settings), and photos / art-piece images (via
"Add a photo", "Add a piece of art", or the "Upload picture" box shown on
any photo/art piece that doesn't have one yet) are all real uploads to
private Supabase Storage buckets (audio and photos); every read gets a
short-lived signed URL generated fresh on each request (see
src/lib/storage.ts), so files are durable across restarts and deploys, and
never publicly reachable.
Photo/art image uploads go straight from the browser to Supabase Storage
using a one-time signed upload token (createImageUploadTicket in
src/lib/storage.ts), instead of through a Server Action — Vercel caps
every function's request body at a hard, non-configurable 4.5MB, and a real
phone photo routinely exceeds that. Song audio and the custom voice sample
are usually smaller and still go through a Server Action (with Next's own
1MB default raised to 4MB in next.config.ts), which carries the same risk
for an unusually large file — a candidate for the same signed-URL treatment
later if it comes up in practice.
A photo or art piece with no image yet still falls back to a labeled color
tile (photos.storage_path / art_pieces.image_path are nullable — the
image is optional, not required) — the same tile shown throughout the app
before real uploads were wired up.
One thing is still deliberately placeholder, flagged as follow-up work rather than attempted here:
Everything else (memories, people, the family tree, song lyrics/notes, books, art pieces, caregiver access) is fully saved to Postgres and reflected across the app right away.
The Photo Jigsaw, "Who's in the Photo?", and "Guess the Artwork" games use
the real uploaded image (its signed URL) when a photo or art piece has one,
and fall back to the same generated placeholder tile
(placeholderPhotoDataUri in lib/ui.ts) otherwise.
Real distinct "AI voices" and real voice cloning both need a paid third-party provider (e.g. ElevenLabs) with its own API key — not something this app can do on its own, and not something to fake with a fabricated integration. Until one is connected:
lib/voicePresets.ts.audio Storage bucket (see "Photos &
audio" above) and selecting it is fully functional end to end, but actual
playback still uses one of the browser's own voices as a clearly-labeled
stand-in, not a real clone of the uploaded voice.Profile.voicePreference
/ Profile.customVoice in lib/types.ts), not in a per-browser cookie like
text size — so it looks and sounds the same for the senior and any
caregiver helping them, on any device.To wire in real AI voices / real voice cloning: call the provider's API
from submitCustomVoice (and a new preset-preview/synthesis path) instead of
SpeechSynthesisUtterance, store the provider's returned voice ID on
custom_voices.sample_audio_path or a new column (see supabase/schema.sql),
and stream back the generated audio for playback.
.btn-lg minimum touch target.VoiceTextField, not a plain
<textarea>, so typing is never the only option.AppHeader so Home / text size / log out stay consistent everywhere.The complete product plan (personas, all four modes, phased roadmap, open
decisions) lives in the "Roots and Echoes" Claude project as plan.md.