个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
Live at (Firebase project buku-rekod).
The latest develop build is on the Firebase Hosting preview channel develop.
Formerly Logbook (Firebase project my-personal-log, deleted September 2026).
A personal super app: a set of small single-purpose mini apps behind one
install. Currently Vehicles (service history and reminders) and
Movies (a watch-later list).
Tap the app icon in the top-left of any page to open the launcher and switch
between mini apps. Each mini app has its own bottom tab bar; Home still opens
straight on the vehicle service calendar.查看完整 README ↗Live at https://buku-rekod.web.app (Firebase project buku-rekod).
The latest develop build is on the Firebase Hosting preview channel develop.
Formerly Logbook (Firebase project my-personal-log, deleted September 2026).
A personal super app: a set of small single-purpose mini apps behind one install. Currently Vehicles (service history and reminders) and Movies (a watch-later list).
Tap the app icon in the top-left of any page to open the launcher and switch between mini apps. Each mini app has its own bottom tab bar; Home still opens straight on the vehicle service calendar.
index.html): one vehicle at a time, picked from a dropdown at the top (remembered per device in localStorage key buku-rekod:selected-vehicle). Under it: the vehicle's card (odometer, last service, most urgent item, ⋮ menu), a month calendar of what's scheduled — a dot per day, red for overdue, amber for due soon — and an "Upcoming & due" list.vehicles.html redirects to Home.vehicle.html?id=<id>): add/edit/delete a vehicle and its service records.movies.html): saved titles, newest saved first, each with poster, type badge and release year. Filter by All / To watch / Watched, or switch on grouping by release year.movie-search.html): search TMDb for a movie, drama or series and save it. Debounced from 2 characters; people and untitled junk results are filtered out.provider:mediaType:providerMediaId rather than the title.sw.js) precaches the app shell, so the app opens instantly and works offline. Movie posters are cached separately in tmdb-posters-v2, which survives version bumps so releases don't force a re-download. Only successful responses are cached, so a failed image can't get stuck.prefers-color-scheme), Light or Dark. js/theme.js loads in every page's <head> so the choice applies before first paint; it's stored per device in localStorage key theme:v1.login.html) via Firebase Authentication.localStorage and syncs to Firestore once configured.localStorage (<localKey>:cache, tagged with the uid, cleared on sign out). View pages boot with bootWithFallback(render, { cached: true }), render that copy immediately, then render again once Firestore answers; forms wait for fresh data but fall back to the copy offline. A save made before the fresh data lands wins over it. Page navigations fall back to the cached HTML after 2.5 s on a slow network.public/ # everything served by Firebase Hosting (the web app)
*.html # one file per page (index, vehicle, movies, …)
sw.js # service worker (offline shell cache, versioned by version.js)
manifest.webmanifest
css/styles.css
js/
core.js # app-agnostic: auth, boot, dialogs, shell, load/save plumbing
shell.js # topbar, app launcher, per-app bottom nav (APPS registry)
garage.store.js # Vehicles data module
watchlist.store.js # Movies data module
backup.js # export/restore every registered store
tmdb.js # TMDb search adapter (the only file that knows TMDb)
<page>.js # one script per page
version.js # single source of truth for APP_VERSION
theme.js # System / Light / Dark choice, applied in <head>
assets/ # PWA / home-screen icons
firebase.json # hosting points at public/
firestore.rules
firestore.indexes.json
APPS in js/shell.js (name, icon, its pages, its bottom tabs).<app>.store.js ending in defineStore({ label, localKey, docPath, ingest, serialize, reset }).core.js then <app>.store.js then your page script, and add the new files to SHELL in sw.js.core.js drives whichever store the page registered, so nothing else needs to change.
Registering also adds the mini app to STORES, so Backup & restore picks it up
automatically — add its store script to backup.html and it's included.
python3 -m http.server 5173 --directory public
Then open http://localhost:5173. With an empty firebase-config.js the app
skips login and runs entirely on localStorage, which is the easiest way to
poke at it.
public/js/firebase-config.example.js
to public/js/firebase-config.js and fill in the values. (firebase-config.js is
gitignored; the web apiKey is a public client identifier, but restrict it
in Google Cloud Console — HTTP referrers + API restrictions — and rely on
Firestore rules to protect data.)Data is stored per user, one document per mini app:
| Mini app | Firestore path | localStorage key |
|---|---|---|
| Vehicles | users/{uid}/garage/main | vehicle-service-log:v1 |
| Movies | users/{uid}/watchlist/main | watchlist:v1 |
firestore.rules scopes users/{userId}/{document=**} to its owner, so every
mini app is covered by that one rule. Neither store is queried — both are
fetched by path — so no Firestore indexes are needed.
Work lands on develop; main only moves when something is released. Live
changes only through a GitHub release. Nobody runs firebase deploy to live
by hand.
| Branch | Environment | URL | Deployed by |
|---|---|---|---|
feature/<name> | none (local only) | http://localhost:5173 | you, with python3 -m http.server |
develop | Firebase Hosting preview channel develop | the channel URL printed in the Preview run log (https://buku-rekod--develop-<hash>.web.app) | preview.yml on every push |
main | live | https://buku-rekod.web.app | release.yml on a published GitHub release |
The preview channel is named, so its URL never changes and only had to be
added to Authentication → Settings → Authorised domains once. It expires
30 days after the last deploy to it; the next push to develop brings it back
at the same URL.
git switch develop && git pull
git switch -c feature/<name>
# ...work, then bump APP_VERSION in public/js/version.js
git push -u origin feature/<name>
gh pr create --base develop
Bump APP_VERSION in every user-facing PR. The service worker's cache name
comes from it, so without a bump, installed copies (the preview included) keep
serving the old cached files. Add a matching entry at the top of CHANGELOG
in public/js/changelog.js: it is the What's new page and the body of the
"it's live" email, and a release without one is refused. When the PR is merged, the push to develop
deploys the preview channel.
git switch main && git pull
git merge --ff-only origin/develop
git push origin main
gh release create vX.Y.Z --target main --generate-notes
vX.Y.Z must equal APP_VERSION in public/js/version.js. The release
workflow refuses a tag that isn't on main, doesn't match the version, or has
no What's new entry. Otherwise it deploys hosting and Firestore rules to live,
then comments on the open Live releases issue with that version's What's
new notes.
Run the release workflow by hand with an older tag. It redeploys that tag's files and rules:
gh workflow run release.yml -f tag=vX.Y.Z
The tag still has to be on main, which every past release is.
Each live deploy comments "vX.Y.Z is live" on the open issue labelled
release-log ("Live releases"). GitHub emails everyone subscribed to that
issue, including whoever opened it. To get the email, subscribe to the issue.
Preview deploys don't comment there, so they stay quiet. A failed run emails
whoever triggered it through GitHub's default Actions notifications.
Repo variables (Settings → Secrets and variables → Actions → Variables) hold the gitignored web config. Each one is the whole file:
| Variable | Written to |
|---|---|
FIREBASE_CONFIG_JS | public/js/firebase-config.js (public identifiers only) |
gh variable set FIREBASE_CONFIG_JS < public/js/firebase-config.js
Repo secrets:
| Secret | What it is |
|---|---|
TMDB_API_KEY | just the TMDb v3 API key; CI writes public/js/tmdb-config.js around it. It is a secret, not a variable, because it is a real API key. Only the key is stored: GitHub masks every line of a multi-line secret, so storing the whole file would mask // and }; throughout the logs |
FIREBASE_SERVICE_ACCOUNT | JSON key of the github-deploy service account |
gh secret set TMDB_API_KEY # paste the key when prompted
The github-deploy service account (Google Cloud Console → IAM and admin →
Service accounts, project buku-rekod) has exactly these roles:
403 Permission denied to get service [firestore.googleapis.com])Don't use the firebase-adminsdk account for CI. It has far broader access.
Rotating the key: open the service account → Keys → Add key → JSON, then:
gh secret set FIREBASE_SERVICE_ACCOUNT < ~/Downloads/buku-rekod-<id>.json
rm ~/Downloads/buku-rekod-<id>.json
Delete the old key on the same Keys tab. buku-rekod-*.json is gitignored, so a
key downloaded into the repo folder can't be committed by accident.
Firebase Auth uids are per-project, so the same person gets a different uid in a new project. Copying Firestore documents directly would preserve the old uid in the path and the app would sign you into an empty account. Use Backup & restore instead — it writes to whoever is signed in, so the re-keying is implicit:
public/js/firebase-config.js.firebase deploy --project <new-id> --only hosting,firestore:rules.firebaserc pins the deploy target: default is the live project, so
firebase deploy needs no --project flag. Keep the old project reachable
under a second alias (firebase use --add) until you're sure the move is
complete — the 2026 migration off vehicle-service-logs did exactly that, and
the alias was dropped once that project was deleted. The Buku Rekod rename
(September 2026) moved from my-personal-log to buku-rekod the same way,
and my-personal-log was deleted once the data was confirmed in the new
project.
Two things that don't move on their own:
localStorage all belong to the
old origin. Delete and re-install the app from the new URL.public/js/tmdb-config.example.js to public/js/tmdb-config.js and paste the key in.tmdb-config.js is gitignored. This is a static site with no backend, so the
key ships in the browser — a TMDb v3 key is read-only and rate-limited, which
is an acceptable trade for a personal app, but don't reuse a key you also use
server-side. Without a key the watchlist still works normally; only the search
page reports that it needs one.
This product uses the TMDb API but is not endorsed or certified by TMDb.