Product tour

What each part actually does.

Not a feature list. This walks through the six things the Hub is built around — identity, credits, documents, people, events and the modules that surround them — and says plainly what is stored, what is computed and what happens when something goes wrong.

Chapter one

Identity and workspaces

The Hub has no password field. Every sign-in is an OAuth 2.0 authorization-code exchange with PKCE against your own World Space ID server, and what comes back is a subject claim — an opaque, stable identifier for that member.

What is storedThe subject, plus a cached copy of the claims you approved — name, email, avatar, tier, card number. Refreshed on every sign-in, so a renamed member is never stale here.
What is notNo password, no password hash, no reset flow, no "forgot your password" email. There is nothing here for an attacker to steal that would let them into your World Space ID account.
The verifierGenerated server-side and never sent to the browser. Someone who intercepts the redirect gets an authorization code they cannot exchange.
Single useThe state value is deleted the moment the callback reads it — before anything else happens. A replayed callback URL finds nothing and stops with a readable page, not a stack trace.
Your workspaceCreated on first sign-in with your brand block and your credit ledger already in place. There is deliberately never a "no workspace" state for the UI to handle.
InvitationsInvite an address before that person has ever signed in, and they join your workspace the first time they do — matched on the verified email claim, so nobody can walk in by typing a colleague's address into their own profile.
Signing out of the Hub ends the Hub session and revokes the tokens this app holds on your behalf. It deliberately leaves your World Space ID session alone — leaving the Hub should not sign you out of your Mile Card dashboard in the next tab.
Chapter two

Credits and payments

One rule holds this together: the balance is the ledger. Nothing anywhere updates a stored number to mean "how many credits you have". Every movement appends a row, and each row records the balance it produced.

Append-onlyNo UPDATE, no DELETE, ever
Row-lockedTwo simultaneous spends cannot both succeed on the same balance
IdempotentA webhook delivered five times grants the credits once
Self-healingA missing webhook is reconciled on the next page load
The three doors money arrives through
DoorWhen it firesWhat stops a double grant
WebhookThe gateway calls us, usually within secondsAll three call one function, which writes a ledger row under the key payment:<order_id>. That key is UNIQUE, so whichever door wins, the member is credited exactly once.
The return tripThe browser lands back from checkout
A status pollAnyone opens a pending order
Forged webhooksRejected. The signature is an HMAC over the raw body with a five-minute replay window; an unsigned "payment succeeded" is logged and refused, never honoured.
Every message keptVerified or not, every inbound gateway message is written down before it is acted on. When somebody says "I paid and nothing happened", that table is the only thing that can answer honestly.
Prices live in the databaseThe client says what it did; the server looks up what that costs. A browser posting {"cost": 0} gets charged the real price.
Running outReturns 402 Payment Required with the exact shortfall — and the record you were saving is still saved. Losing typed work to protect a counter is the wrong trade.
RefundsReverse the credits with a new ledger row rather than editing the purchase. The purchase happened; the ledger says so.
Chapter three

Space Docs — letterheads and invoices

Set your brand once and every document inherits it. Change the company address and it changes on the next document — not on two hundred old ones, which is why the brand is its own row rather than a copy stamped onto each letter.

Four letterhead templatesClassic, colour band, centred crest and minimal — each rendered as a real A4 sheet with a live preview that scales to fit, and a print path that produces the actual page.
Five document kindsInvoice, proforma, quotation, receipt and credit note, all sharing one numbering series and one register.
The tax ruleTax is charged on the discounted line value, not the gross. A 10% discount on a taxed invoice reduces the tax too — which is what every accountant expects, and the single most common way a generated invoice ends up a few rupees wrong.
Derived statusDraft and Cancelled are decisions a person made and are respected. The rest follows from the money and the calendar: fully paid is Paid; past due with anything outstanding is Overdue — that beats a part payment, because a half-paid invoice three weeks late is still late.
PrefillAn ERP sales order or a CRM deal becomes a draft invoice with the client, the reference and the line item already filled in.
The aging reportCurrent, 1–30, 31–60, 60+ — answered by one indexed query against cached minor-unit totals, not by loading every invoice into memory and re-doing the arithmetic.
Three tables, not one JSON blob: header, line items and payments. The questions people actually ask of invoices — what is outstanding, what is overdue, what did this client pay us last quarter — are SQL questions, and a document store cannot answer them honestly.
Chapter four

Space People — the employee record

The longest-lived object in the product. It outlasts every project the person touches, so it gets real columns, real dates and real foreign keys — and five child tables, because each of them is a list somebody queries on its own.

One profile, eight tabs
TabWhat lives there
OverviewContact details, address, emergency contact, skills, direct reports
Job & payTitle, department, manager, type, mode, tenure, band, CTC, statutory IDs, salary account
DocumentsOffer letters, contracts, IDs, certifications — stored with the record and downloadable again later
LeaveBalances against quota, requests with approve/reject, and a workspace-wide approvals queue answered by a join
AssetsWhat was issued, when, and whether it came back
OnboardingAn ordered checklist with progress, editable per person
Speaker profileThe switch that makes this person available to the Event Producer, with the bio that gets read on stage
Notes1:1s, milestones, concerns — timestamped and attributed
A partial save never destroys what it did not mention. Send {"id": …, "status": "On leave"} and only the status changes — the job title, the bank details and the document file are all still there. Without that rule, a status dropdown quietly empties a personnel file and nobody notices until the audit.
Chapter five

Event Producer

A production is a graph, not a document. Days hold sessions; sessions reference tracks and speakers; speakers appear in many sessions. Stored as JSON, the one question the product exists to answer — what is on which stage at 3pm, who is on it, and what is the booth doing — would need every event in the workspace loaded to answer.

10screen modes, every one rendered rather than described
7music cue points, with fade and volume in real numbers
12session types, from registration to closing
1keypress to put the slate on the projector
Tracks & stagesColour-coded, room-assigned, and carried through the lineup, the run sheet and Show Mode so a session's colour means the same thing everywhere.
The rosterSpeakers, panelists, moderators, hosts and performers with status, bio, topics, travel and hospitality. Pull anyone with a stage profile straight out of Space People.
The lineupMulti-day, time-ordered, with gaps flagged as clickable ("+ 25 min gap — click to fill") and overlaps flagged in red before they become a problem on the day.
Screen cuesSpeaker deck, lower-third, countdown, agenda board, sponsor loop, live camera, Q&A, holding slate, blackout, custom — each with its own slate copy and a note for the vision desk.
Music cuesCue point, track, source deck, fade in, fade out, volume. The booth gets numbers, not adjectives.
Show ModeRuns the day live: the current session with an overrun-aware countdown that turns red when you go long, the screen slate, the audio cue, next up, booth notes, a scrubable rundown and a GO button. P throws the slate full-screen for the projector.
On paperA printable run of show per day and a speaker pack, generated from the same rows — so nobody re-types the schedule into a spreadsheet at 2am and introduces a discrepancy.
Chapter six

Space Decks

A real canvas, not a template filler. Drag anything, resize anything, double-click text to type into it, and every element keeps its position as a percentage so a slide looks the same on a phone thumbnail and a 4K projector.

Nine layoutsTitle, title + body, bullets, two columns, section break, quote, big number, image + caption, blank — a starting point, not a cage.
Six themesEvery element colour is a role (text, muted, accent, surface) rather than a hex, so switching theme restyles a finished deck instead of breaking it.
Present modeFull screen, arrow keys, a timer you can pause, and speaker notes on N.
ExportOne self-contained HTML file that runs offline with its own keyboard navigation — no viewer, no account, no internet needed in the room.
Attached to a sessionPoint a session at a deck and Show Mode tells the booth which presentation to bring up, and when.
The deck header and each slide are database rows; a slide's elements stay JSON. That split is deliberate: "which decks are unfinished" and "has this speaker sent their slides" are queries at the slide grain, while a text box's x/y/width is only ever read back whole to redraw the canvas.
Chapter seven

The other nineteen

Everything else in the suite, and what it is for. All of it persists to your workspace, all of it respects the same roles, and all of it shares the identity you signed in with.

Chapter eight

How it is built

Plain PHP 8 and MySQL on the server, one bundled React app in the browser, and no framework to keep up with. It runs on shared hosting because most of the world's organisations are on shared hosting.

Browser

One bundled app. Twenty-five modules, one design system, light and dark.

Reactesbuildno CDN at runtime
↓  same-origin fetch, credentialed cookie
API

Prepared statements everywhere, workspace scoping on every query, an Origin check on every state-changing request, and rate limits counted per key and per IP.

PHP 8no Composerno frameworkJSON only
Database

Thirty-eight tables. The money and records modules are normalized because they are queried; the document-shaped modules use a versioned per-workspace store with optimistic concurrency.

MySQL 8 / MariaDB 10.4+InnoDButf8mb4foreign keys
↕  OAuth 2.0 + PKCE
World Space ID

Your existing identity server. The Hub is a registered client of it and holds nothing but a subject claim and a rotating refresh token, encrypted at rest.

authorization_codeS256refresh rotation
/api/health.php checks the database, all thirty-eight tables, the encryption key, the identity-provider credentials, the base URL, the payment gateway and the uploads directory — and names the specific thing that is wrong rather than returning a bare 500. It is written for whoever is deploying this at 1am.
Open World Space Hub

Bring your World Space ID. Nothing else to set up.