01 Privacy
What we hold, and where it actually is.
The planner needs an account, and PC Fitness is invitation-only, so it holds three things about you: an email address, the floor plans you draw, and the record of how you came to be let in — the invitation the owner opened for your address, or the request you filed asking for one. This page says exactly what that means, in the order the data moves. It was written by reading the source, and anything that could not be pointed at a file was left out.
Last updated 2026-08-27 · applies to pcfitness.app and app.pcfitness.app
The short version
Your email address is held by Firebase Authentication, a Google service. Your floor plans are held in Cloud Firestore, also Google, under a path only your own account can read. Working copies also sit unencrypted in your browser's storage on every device you have used, and signing out deliberately does not erase them. There is no advertising, nothing is sold, and no analytics product is built into the planner — but Cloudflare Web Analytics is currently switched on for the hosting, and it counts page views. It sets no cookie and follows nobody between sites. It is being turned off; until it is, this page says so rather than claiming otherwise.
Two things about who can read what, and they point in opposite directions. Nobody operating PC Fitness can read your plans — no rule anywhere under users/ mentions an administrator, so there is no path to your drawings, not as a policy but as an absence in the file. The owner can read every email address the product holds, because the allowlist, the invitation list and the waitlist are lists of email addresses and the owner has to be able to work them. Both of those are stated in full below.
What an account holds
If you sign up with an email address and a password
Firebase Authentication holds your email address and a hash of your password. The password itself is handed straight to the Firebase SDK by app/src/core/firebaseClient.js and is never written to storage, never logged and never sent anywhere else by this application. Firebase also keeps its own record of when the account was created and when it last signed in.
If you sign in with Google
Google returns a Google account identifier, your email address, your display name and the URL of your profile picture. Those four fields are the whole of what the app keeps for a signed-in session; nothing else from your Google account is requested and nothing else is stored. Choosing Google also means Google knows you signed in here, which is true of every site that offers it.
If you use a sign-in link
The address you asked the link to be sent to is written to your browser's storage so the link can be completed when you come back. Firebase sends the mail.
Getting in: what is recorded when you are invited, or when you ask
PC Fitness is invitation-only while it is in beta. Having an account is not the same as being let in: the database rules refuse to save a project for an account that is not on the allowlist, so being approved is a separate record from being signed up. Four collections in Cloud Firestore hold that, and all four are keyed to a person. This is the whole of them, field by field, from firestore.rules and the panel that writes them.
If the owner invites you — invites/
This document is keyed by your email address itself, in lowercase: the address is the document's name, not a field inside it. It is created when the owner invites you, whether or not you ever sign up, and whether or not you reply. If you were invited and never came, the record of the invitation is still there. It holds:
- your email address, lowercased — as the document's name and again as a field;
- a status, which is either invited while the invitation can still be used or claimed once it has been;
- when it was issued, and the account id of the administrator who issued it;
- a short note the owner may have typed about you, up to 200 characters — it is their note, written by them, and you do not see it;
- a count of how many times the owner opened a draft invitation email from the panel, and when they last did. That counts clicks in the panel; nothing in this product can observe whether mail was delivered or read;
- the id of the request this invitation answers, when it answers one.
The invitation email itself is not sent by this product. There is no server here and no mail credential anywhere in it: the panel composes the message and hands it to the owner's own mail client, and the owner sends it from their own mailbox (app/src/admin/inviteMail.js). So the delivery is an ordinary email from one person to another, and it is governed by whatever mail provider the owner uses, not by anything described on this page.
If you ask to be let in — requests/
Written by you, from your own browser, and keyed by your account id, so there is one per account. Exactly six fields, pinned by the rules so that none of them can be somebody else's:
- your account id;
- your email address, lowercased, taken from your sign-in token and not typed;
- whether that address had been confirmed at the moment you asked — recorded, not required, because asking is not entering;
- a free-text note you wrote, up to 500 characters. Whatever you put in it, the owner reads. It is the one field on this page whose contents are yours to choose, so choose them knowing that;
- a short word saying where the request came from;
- the time you asked, taken from the server's clock rather than your computer's.
The owner may afterwards add a decision to your request — approved or declined, when, and by which administrator account. You cannot edit or withdraw a request once it is filed; the rules give both of those to administrators only, deliberately, so that declining somebody is not undone by their re-filing. If you want your waitlist entry erased, ask at [email protected] and the owner deletes it.
Once you are in — access/
The allowlist. Keyed by your account id, and its mere existence is what approves you; deleting it is what revokes you. It holds your account id, your email address in lowercase, which of the two routes let you in, and when. A grant made by the owner directly also records which administrator account made it.
Being revoked does not delete your plans and does not lock you out of them. The rules require approval to write a project and deliberately do not require it to read, export or delete one, so somebody removed from the beta can still open everything they drew, download it and erase it. That asymmetry is written into firestore.rules with the reasoning next to it.
If you are removed — blocked/
Being removed from the beta writes a second record, keyed by your account id, holding your account id, your email address in lowercase, the time, the administrator account that did it, and a short note that administrator may write. It exists because removing somebody has to be about the person rather than about an email address: an invitation is addressed to an address, one person can be invited at two addresses, and without this record an older invitation at the second address would quietly let a removed account back in. The rules refuse to delete an approval unless this record is written in the same operation, so the two cannot come apart.
It outlives the approval it removed — that is the whole point of it, since a record that disappeared when access did would stop working exactly when it was needed. Like the three above, deleting your account does not remove it; ask at [email protected] and the owner deletes it.
What your projects hold
Every project is stored under users/{your account id}/projects/ in Cloud Firestore, split into two documents:
- a small metadata document — the project name you typed, a schema number, the last modified time, a content hash and your account id;
- a payload document holding the entire project as one JSON string: the shell you drew, its openings, the zones, every piece of equipment and where you put it, and the program settings that produced the layout.
That is the whole record. There is no usage log, no edit history sent anywhere, and no field recording what you did in the app. The database lives in Google's nam5 multi-region, which is in the United States — Iowa and South Carolina. That location is fixed for the life of the database and cannot be moved.
Who can read it
Your projects: only you
Every read and every write under users/ in firestore.rules is gated on the requester being the owner of the path, and list queries are capped. There is no administrative clause anywhere under that path — no support override and no back door — so no operator of this product can open, list or export anybody's drawings. That file is the entire server boundary: your browser talks to Google directly, nothing of ours sits in between, and there is no deployed service of ours that could be granted a permission it does not have.
Your email address: the owner can read it, and does
This is the part a reader will otherwise assume the other way round, so it is said plainly. An administrator — in practice the one person who runs this — can list access/, invites/ and requests/ in full. That is every email address this product holds: everyone approved, everyone invited whether or not they ever signed up, and everyone who has asked, together with the note they wrote. The rules grant it in so many words (allow list: if isAdmin(), capped at 500 rows a query), because an invitation list nobody can read is an invitation list nobody can work.
What being an administrator does not buy is anything at all under users/. The two grants are separate on purpose: the administrator clauses exist only in those four collections plus the list of administrators itself, and adding one under users/ is the change this design exists to refuse.
Nothing of ours sits in the middle
To be precise about "nothing of ours": the source tree does contain two Node programs, and neither is deployed or reachable from the internet. server/ is a localhost file and job server for developing the app on one machine — it listens on 127.0.0.1:3000, reads and writes project files on that machine's own disk, and is not part of the hosted product. worker/ is the offline video renderer: it drives a local copy of Chrome and a local ffmpeg to turn a plan into an MP4 on your own GPU. Nothing runs on a server we operate, because there is no server we operate. What is deployed is two folders of static files on Cloudflare Pages, and firestore.rules running inside Google's database.
The practical consequence is worth saying out loud: if you lose access to your account, nobody here can read your plans back to you, because nobody here can read them at all.
What is kept on your own device
The planner keeps working copies in your browser's localStorage so a reload reopens where you were and a failed save never loses work. This is plain text on your disk. It is not encrypted, and signing out does not clear it — that is deliberate, because signing out is not a request to throw work away, but it means a shared or borrowed computer keeps your floor plans until the site's data is cleared. Use your browser's "clear site data" for app.pcfitness.app when you are done on a machine that is not yours.
Browser storage is per website, never per person, so everything that holds one person's work carries the account it belongs to — either under pcf.u.<account>. or with .u.<account> on the end — and a second person signing in on the same laptop gets their own set. That was not always true. A browser that used an earlier build may still be carrying the unscoped keys those builds wrote, and they are listed at the bottom of the table below. Clearing the site's data removes those too.
Every key the planner writes, as of the date above. This list is derived from the source — every call that puts a value into browser storage, followed back to the code that builds the key — and not from the previous version of this page.
-
pcf.u.<account>.project.<id>
pcf.u.<account>.projects.index - A mirror of each project this browser has opened or saved, and the list of them. This is the floor-plan data itself, in full.
- pcf.u.<account>.sync.<id>
- What this browser and your account last agreed the project was — a hash, your account id, a tab id and a timestamp. It is how a second tab's save is noticed instead of silently overwritten.
-
pcf.u.<account>.recovery.index
pcf.u.<account>.recovery.<n> - The recovery bin: copies held back from a delete or a lost conflict. Whole projects.
-
pcf.u.<account>.lifeboat.<id>
pcf.u.<account>.lifeboat.index - Copies kept when a save to your account failed, so the work survives until it succeeds. Whole projects.
- pcf.ui.keptCopy.u.<account>
- Projects parked when a save went wrong or a draft could not be opened. Whole projects.
-
plancrafters.project.v1.rejected.u.<account>
plancrafters.project.v1.rejected.prev.u.<account> - The last drafts that failed to load, and the slot behind them — kept rather than discarded, because a draft the app cannot read is still your work.
- pcf.draft.parked.u.<account>
- Your draft, set aside while somebody else is signed in on this browser, so two people sharing a machine do not inherit each other's work. A whole project.
- plancrafters.project.v1
pcf.draft.owner - The working draft, rewritten on every change so a reload reopens where you were, and the label saying which account it belongs to. There is one planner, so there is one draft slot and it carries no account in its name — the label is what makes it attributable, and it is what stops the next person to sign in on this machine from picking the draft up.
- pcf.ui.projectStats.v1.u.<account>
- Facts about each row in the Open dialog — piece count, floor area, equipment cost — cached so the library opens without re-reading every project.
- pcf.ui.openIntent.u.<account>
- A two-minute note to the next page load saying which project you asked to open. It holds the project's name. Deleted on the first read.
- pcf.auth.session
pcf.auth.redirect
pcf.auth.email - Sign-in bookkeeping. The last one holds the address a sign-in link was sent to. All three are cleared when you sign out, that one included — so on a shared machine the next person's sign-in link is never completed with your address.
- pcf.catalog
pcf.rules.<pack> - The equipment catalog and the rule pack, cached so the app starts on a slow connection. Identical for every account and nothing to do with you.
-
pcf.renderPanel.dismissed.v1
pcf.renderPanel.calibration.v1
pcf.plan2d.legend - Interface preferences: which render-panel notices you have dismissed, its calibration settings, and whether the plan legend is open. No project data.
-
pcf.projects.index
pcf.project.<id>
pcf.sync.<id>
pcf.recovery.index
pcf.recovery.<n>
pcf.lifeboat.<id>
pcf.lifeboat.index
pcf.ui.keptCopy
pcf.ui.projectStats.v1
pcf.ui.openIntent
pcf.draft.parked.<account>
plancrafters.project.v1.rejected
plancrafters.project.v1.rejected.prev - Written by earlier builds, before the keys carried an account. Nothing writes to them now — they are only read, so that work saved by an older version is not stranded, and they are removed as they are read or when the account they belong to is erased. Two of the index keys are rewritten while entries are being taken out of them, which is a deletion and not a new record. If you used the planner on this browser before the accounts release, they are probably still there and clearing the site's data is what removes them.
Firebase Authentication also keeps your session token in browser storage. That is how you stay signed in between visits.
The three third parties, and what each one sees
Google — Firebase Authentication and Cloud Firestore
Holds the account and the projects, as described above. Your browser talks to Google directly for both. Google's own terms and privacy notice govern what Google does with the traffic it receives.
Google — reCAPTCHA v3, through Firebase App Check
When App Check is configured for a build, the planner loads reCAPTCHA v3 from google.com/recaptcha and gstatic.com/recaptcha. It scores your browser so a stranger holding the public API key cannot drain the database quota. reCAPTCHA is a Google product, it does its own inspection of the browser to produce that score, and Google's terms apply to it.
When it loads matters, so here is exactly when. It is started by ensureAppCheck() in app/src/core/firebaseClient.js, which is called from one place only: ensureDb(), the function that builds the Firestore client the first time the planner reads a project from your account or writes one to it. It is not loaded to render the sign-in form and it is not loaded to sign you in. Loading the app while signed out fetches nothing from google.com or gstatic.com at all — that was measured against a build with the real site key, by reading the browser's own record of what it fetched, not assumed.
This was the other way round in an earlier build, and the honest thing is to say so: App Check was started on the boot path, so a visitor who had not signed in — who might never sign in — was scored by Google anyway. That is fixed, and this paragraph describes the code as it is now. The marketing site you are reading has never loaded it: its policy is connect-src 'none'.
Cloudflare — the edge that serves both sites
pcfitness.app and app.pcfitness.app are served from Cloudflare Pages, so Cloudflare handles the request and sees what any content network sees: your IP address, the URL, the user agent, the time. Bot protection and one rate-limiting rule are enabled at that edge, and a challenged request can be given Cloudflare's own cookie. Cloudflare does not sit in front of the database or of sign-in: that traffic goes from your browser straight to Google, and Cloudflare never sees it.
What is not done
Each of these was checked against the source and the built bundle before it was written down, not assumed.
-
No analytics in the planner itself; page counting at the edge, for now.
There is no analytics product in the build — no Google Analytics, no tag manager, no Segment, PostHog, Mixpanel, Hotjar, and no error reporter. What there IS, and the build cannot show you because it is added by the host rather than by us, is Cloudflare Web Analytics on app.pcfitness.app: a small script injected at the edge that counts page views. No cookie, no identifier that follows you to another site. It is being switched off, and this row will say so plainly when it has been — it is written down now because a privacy notice that is accurate only about the parts we compile ourselves is not accurate. The planner's Content-Security-Policy names every origin it may reach, in any way, and nothing else can be reached even by accident: itself, and ten Google origins. They are listed one by one below this table. This marketing site is stricter than any of it — its policy is default-src 'none' with connect-src 'none', so the page you are reading cannot make a network request at all.
-
No tracking pixels, no advertising, no ad network.
Images on this site may only be loaded from this site. There is no advertising anywhere in the product, and no ad or attribution network is embedded in either page.
-
Nothing is sold, rented or handed to a data broker.
Your email address and your floor plans are not sold, not shared for anyone else's marketing, and not used to train anything. The only parties that hold them are the ones named above, and they hold them because they are the hosting.
-
PC Fitness sets no cookies.
Neither page sets one — the app uses browser storage instead, and there is no document.cookie anywhere in the source. Cloudflare's bot protection, and Google's sign-in and reCAPTCHA, may set their own. Those are theirs, not ours, and we cannot see them.
-
No marketing email, ever.
There is no mailing list and no bulk mail of any kind. Nothing in this product can send mail at all: there is no server, no SMTP credential and no mail provider anywhere in it, and the support address is inbound-only forwarding. Two kinds of mail can still reach you, and both are named rather than implied. Firebase sends the ones you ask for — a password reset, a sign-in link, an address-confirmation link. And the owner may write to you personally, from their own mailbox, about your invitation or your request to join; the panel composes that message and hands it to their mail client, and a person presses send.
Every origin the planner may reach
Read off the deployed header — app/dist/_headers, which is the built copy of app/public/_headers — rather than from the previous version of this page. Ten origins, all of them Google's, plus the app itself:
- What it sends data to and receives data from. identitytoolkit.googleapis.com (sign-in), securetoken.googleapis.com (staying signed in), firestore.googleapis.com (your projects, and the approval records described above) and firebaseinstallations.googleapis.com. A build with App Check switched on adds firebaseappcheck.googleapis.com, content-firebaseappcheck.googleapis.com and www.google.com/recaptcha/.
- Where it may load code from. apis.google.com, which is Google's own bootstrap script for pop-up and redirect sign-in. App Check builds add www.google.com/recaptcha/ and www.gstatic.com/recaptcha/.
- What it may put in a frame. pcfitness-app.firebaseapp.com, the hidden helper page Firebase's sign-in creates. App Check builds add the reCAPTCHA challenge frame at www.google.com/recaptcha/.
An earlier version of this page named seven of those and called the list exhaustive. It was counting only the first group, so the sign-in bootstrap script, the reCAPTCHA script host and the sign-in helper frame were missing from it. All three are, and always were, in the deployed header; the miscount was on this page, not in the policy.
How long it is kept
A project stays in your account until you delete it. Deleting a project in the planner removes both of its documents together, so nothing is left stranded behind the listing. Your account and its email address stay until the account is deleted. The copies in your browser stay until you clear that browser's data for the site — deleting a project clears that browser's mirror of it, but not the mirrors on your other devices, which go when those browsers next reconcile with the account or when you clear their site data.
The approval records are on a different clock, and it is a longer one. An invitation stays until the owner withdraws it, whether or not it was ever used and whether or not you ever signed up. A waitlist entry stays until the owner deletes it. An allowlist row stays until you are revoked. None of the three is removed by deleting your account — see below, where that is set out rather than skated over.
Google keeps its own operational backups of Firebase projects on its own schedule. We do not control that schedule, and this page will not state a number for it until the answer has been checked rather than guessed.
Getting a copy of your data
The export already exists and it is complete. In the planner, open File ▸ Export ▸ Project file (JSON). What downloads is the same JSON that is stored in your account, byte for byte — that payload document is the entire record of the project, so the export is not a summary of your data, it is your data. File ▸ Export will also give you the floor plan as a PDF and the equipment list as a CSV if you want them in a form you can read.
What the export does not include is everything that is not a project: the email address on your account, which you already know and which is shown in the account panel, and the approval records described above — your allowlist row, the invitation keyed by your address, and any request you filed. Those are listed field by field on this page instead, which is the whole of them; if you want a copy of your own three documents, ask at [email protected].
Deleting your account and everything in it
In the planner, open the account panel and choose Delete my account and everything in it. It offers to download every project first, then asks you to type the account's email address, and then works in three steps in one order and only that order:
- every project document and payload document in your account, and every copy of them this browser is holding under your account — the mirror, the recovery bin, the copies held back while saves were failing, the parked drafts;
- the cached interface records listed in the key table above;
- the sign-in itself — your email address, and your password or Google link.
The order is not a preference. Firebase does not cascade, and your account is the credential the database rules check on every single one of those deletes. Removing it first would leave your floor plans in the database with no owner, reachable by nobody and deletable by nobody — including us. So the planner refuses to touch the sign-in until the projects report back as genuinely gone, and if any of them refuse, it stops and tells you on screen that your sign-in is still there and why.
Read what it tells you at the end. It names what was deleted and what was not, rather than saying "done". If it reports that your projects were deleted but the sign-in was not, that is a real state and not a display quirk — write to [email protected] from the address on the account and it will be finished by hand.
None of it can be undone, and because nobody here can read your projects, nobody here can restore them either. Take the download it offers you.
What deleting your account does not remove
Your email address stays on the approval records, and this page will not pretend otherwise. The delete described above erases your projects and your sign-in. It does not touch access/, invites/, requests/ or blocked/ — there is no code in the product that deletes them, and the rules would refuse you if there were: deleting all four is granted to administrators and to nobody else, including the person the records are about.
So after you delete your account, your allowlist row is still there, so is any request you filed with the note you wrote in it, so is any invitation keyed by your address, and so is any removal record. The last one is the plainest: an invitation list is keyed by address precisely so that it works for people who have no account, and that is the same property that leaves it standing when an account goes away. It is a gap rather than a choice, and the honest thing is to say which. Until the product deletes these itself, the way to have them removed is to write to [email protected] and ask; the owner can delete all four and nobody else can.
Deleting your account also does not reach into the browsers you have used. Clear the site data on each device to remove the local copies described above.
Children
PC Fitness is a tool for laying out commercial gyms and is not aimed at children. It does not ask your age and does not currently enforce a minimum one. If you believe a child has created an account here, write to [email protected] and it will be deleted.
Who runs this, and what this page does not claim
PC Fitness is a personal project run by one person, not a company. There is no legal entity behind it, and this page does not claim to be certified under, or compliant with, any particular data-protection regime — a claim like that is worth nothing until somebody qualified has checked it, and nobody has.
What is true wherever you are, stated with its one exception rather than without it: everything held about you is described on this page, your projects can be exported in full and deleted by you without asking anyone, and the sign-in with them. The exception is the three approval records — the allowlist row, the invitation and the request — which you can read here but cannot export or delete yourself, and which the owner deletes on request. The data itself sits in the United States, in the Firestore region named above.
Security reporting
If you find a way to read someone else's projects, or anything else that looks like a hole, please report it rather than testing how far it goes. The route and the ground rules are at /.well-known/security.txt.
Changes
When the product changes what it collects, this page changes in the same breath and the date at the top moves. There is no archive of earlier versions; if that matters to you, keep a copy.
Contact
[email protected]. It is a new address and it is read by one person, so allow a few days.