Tập Nhạc

Privacy

Tập Nhạc is local-first. Your library, your passages and your practice history live on your own device, and nothing leaves it unless you sign in and use a feature that needs the cloud.

This page describes what the app does today. It is written from the code, not from a template.

What stays on your device

Everything by default. Scores, passages, practice history, settings, fingerings, collections and editor drafts are stored in your browser's IndexedDB database named primavista, through Dexie. A small amount of interface state (theme, remembered username, which tours you have seen, the welcome flag) is kept in localStorage.

Some things never sync at all: the thumbnail cache rebuilt from your scores, the instrument-pack registry, the three settings that describe one device (MIDI device, latency offset, keep awake), editor drafts and versions, the soundfont and engraving caches, and the service worker cache.

You can use the whole practice loop (import a score, pick a passage, drill it) with no account and no connection. Once the app has loaded it works offline.

Optional account

An account is optional and is only needed for sync, sharing, classes and Compose with Claude. It has a username you choose and a passkey (WebAuthn). There is no email address, no password and no phone number.

The server stores the username, the public half of the passkey and its signature counter, and a one-time challenge while a sign-in is in progress. A successful sign-in sets a session cookie named pv_session: httpOnly, SameSite=Lax, 30 days, and Secure when served over HTTPS. That cookie is the only cookie the app sets.

Cloud sync and backups

When you are signed in, your device pushes the rows it changed and pulls everything newer than its last cursor. What is uploaded:

  • Pieces, including their score files and metadata.
  • Passages (the bar ranges you practise) and their progress.
  • Practice history, packed one row per piece and day rather than one row per note.
  • Practice sessions, fingerings, collections and your own bar notes.
  • Settings, except the three device-only fields listed above.

Rows are stored in Cloudflare D1. Score files are stored in Cloudflare R2 by content hash, under your user id. Every read and write on the server is scoped to the signed-in account; the server validates each row and never inspects musical content.

Deleting. Deleting a piece, passage or other synced row records a tombstone, so the delete reaches your other devices. A daily job at 03:17 UTC removes tombstones older than 90 days, and removes score files that no live piece references once they are more than 30 days old. A device that has been offline for longer than that resyncs from scratch.

Signing out leaves your cloud copy in place. You can also tick "Also remove my library from this device" to wipe the local copy. There is no delete-account button in the app today, so signing out (even with the local library removed) does not remove the cloud copy.

Older versions of the app also uploaded one whole-library backup file per account. The current version reads it once to migrate and no longer writes it.

Shares and class links

A score is private by default and never leaves your device. Publishing one uploads a snapshot: a row in D1 and the MusicXML file in R2.

  • Public: anyone with the link can open it, and search engines may index it.
  • Unlisted: anyone with the link can open it, but the page asks search engines not to index it.
  • Private: it stays on your device. A missing, foreign, private or unpublished link answers the same "not found", so an id cannot be probed.

Unpublishing deletes the score file and the link stops working. Comments on a share are off by default; the owner can turn on "Show comments" and "Allow comments". Comment bodies are plain text, and author usernames are visible while comments are shown.

Class links. A teacher creates a class and gets an invite link. The server stores only a hash of the invite token, so the link is shown once and can be rotated or turned off. A student who opens it sees the consent text before joining: their teacher will see practice minutes, accuracy and memory for assigned pieces.

What a teacher receives is only the aggregate per assignment: minutes, accuracy, passages learned, memory, tempo and the last practice time. The progress schema is strict and rejects raw note-by-note attempts, and the classes API has no path to a student's sync rows. Class scores live in R2 and are returned only to the teacher and to members who have that piece assigned.

A parent link is read-only, covers one student, and shows aggregates and piece titles only: no scores and no other students. It expires after 90 days by default, can be revoked, and ends when the student leaves the class. Leaving or being removed from a class ends access at once and deletes that student's private threads.

Compose with Claude

The editor's "Compose with Claude" panel needs a connection and a signed-in account. Nothing is sent anywhere unless you use it.

When you do, your prompt and the score text the model reads are sent from the browser to the app's own Worker, which forwards them to Anthropic's Messages API. The API key stays on the server: it never reaches the browser and is never written to logs (a failed upstream call logs only its status).

Tập Nhạc keeps no copy of the conversation. It stores only a call count in D1, used for the daily limit of 60 calls per user. Anthropic's handling of the request is covered by Anthropic's own terms.

No ads, no tracking cookies

There is no advertising and no cross-site tracking. Page views are counted with Cloudflare Web Analytics, which sets no cookies and does not follow you across sites. The only cookie the app sets is the session cookie described above, and only while you are signed in.

When the app crashes, it sends one small report: the error text, the route pattern (ids and invite tokens are stripped), the app version and your browser family (for example "chrome"). It carries no account, no IP address and nothing from your scores, and it is stored only as a count per error and hour.

Fonts, the piano soundfont and the bundled scores are served from the app itself, not from a third-party CDN. localStorage holds interface preferences and flags, not identifiers used to follow you.