Skip to content

Areas, roles and code size

Rule. Every optional feature is a switchable area (the code calls it a module), registered in one place. Off means gone everywhere. Switching off keeps the data; deleting it is a separate, explicit purge.

Why. A space that does not run events must not see event menus, dead links or empty reports. Half-visible features confuse people and leak data.

How to apply.

  • One registry holds each area’s key, label, description, group, dependencies, default and whether it is core. Defaults, workflow lists and the feature catalogue derive from it; never keep a second list.
  • Two levels: platform switch (the owner allows it on this install) and per-space switch (a space turns it on). A feature inside an area has its own key under the area.
  • When an area is off: hide it in navigation, tabs, widgets and search; refuse its routes with a typed “not found” that names the area; leave it out of reports; skip its notifications and background jobs; remove it from public pages.
  • Opt-in, default off for anything not needed to run a basic space. Core areas that a hard rule depends on (public catalogue, request workflow, evidence upload, QR) cannot be switched off. Personal accounts are always on; reports are on by default.
  • Switching off is reversible; purging is not. Purge is a second, privileged step that deletes one area’s rows. No single action may both hide and destroy. A purge never deletes a payment record.
  • A retired key is mapped to its replacement, never offered again, so old backups and API clients keep working.
  • Installers ask about areas, not module keys, in plain words, with presets.
  • Changing a key means editing the user-facing modules page in the same change.

Across the org. SpaceWorks: full registry, platform and per-space switches, install, uninstall and purge, drift-guard tests. Workspace: product areas (work, chat, knowledge, people, operations, finance and others) with one navigation config and docs/areas/; per-space on/off switches not checked. Events: instance settings switch paid events and other features on; a general area registry not checked. Machines, Inventory, Bookings, Organization: single-purpose products (one area each); per-feature switches not checked. Android and iOS: show only what the server’s area and role payload allows (fixed-tenant staff authority).


Rule. Permissions are action-based. Screens ask “may this person do X here?”, never “is this person an admin?”. Every query is scoped to the tenant. The interface shows only what the role can use, and the server refuses the rest.

Why. “Admin or not” is too coarse. A front-desk volunteer needs a small, safe console; a finance role should not see staff settings. And a missing scope is a cross-tenant data leak.

How to apply.

  • Define roles as sets of capabilities in one file (for example lib/permissionRoles.ts, lib/roles.ts, apps/accounts/rbac.py). Allow custom roles built from a fixed list of capabilities, with protected default roles seeded per tenant.
  • Check capabilities (requireCapability, can(actor, action, resource)), never role names, in pages, actions, API routes, notification recipients and reports.
  • Area hide: navigation, tabs, widgets, report rows and search results show only what the role holds. Hiding is not security; the server checks again.
  • Narrow consoles for narrow roles. A handover or front-desk role gets a short console with no dashboard widgets or unrelated queues. Area managers see the whole of their own area (finance, to-buy, repairs, reports, audit); the top manager sees all areas combined and grouped.
  • Three surfaces stay distinct: staff, member and platform admin. Staff always use the Space Works palette; members may use derived tenant colours.
  • Platform admin is separate and global. It opens only from the server-set identity, with a fresh second factor, answers a plain “not found” to everyone else, and is web-only: the phone apps refuse platform-admin accounts.
  • Cross-tenant access returns 404, never 403, so a slug never reveals that a space exists.
  • Every tenant always keeps one active top-level owner; admins cannot act on other admins or owners.
  • Staff need an authenticator (TOTP) before the staff console opens. Borrowing or membership standing (good, restricted, suspended) is separate from the role.
  • The same person has the same authority on web and on a native token.

Across the org.

Product Roles today
SpaceWorks Per-space editable roles; defaults Space Manager, Inventory Manager, Machine Manager, Member; custom roles for handover; platform superadmin
Workspace OWNER > ADMIN > MEMBER > GUEST, capabilities in lib/roles.ts; super admin with 10-minute TOTP step-up
Events Organisation OWNER/ADMIN/MEMBER plus event co-organiser, judge and mentor grants; staff MFA each session; super admin
Machines Machine manager, machine type manager (scoped to types), operator, technician, member; capabilities in one file
Inventory Inventory manager, staff, member, technician (no admin or owner role); ROLE_MATRIX
Bookings Bookings manager, front desk, viewer, technician
Organization Custom OrgRoles per organisation from a fixed permission list, plus unit hierarchy scoping
Android / iOS Staff screens permission-aware; tablet and phone; superadmin refused

Rule. About 200 lines per file is the target; 300 is a hard ceiling. One responsibility per file.

Why. Small files can be read, reviewed and tested in one sitting. Large ones hide bugs and make every change touch unrelated code.

How to apply.

  • Split by responsibility (routes, validation, service logic). In stack A use submodules (views_*, serializers_*, services_*) behind a thin re-export barrel with explicit imports, never import *. In stack B turn lib/area/service.ts into lib/area/service/ with an index.ts re-export so import paths stay stable.
  • Never meet the limit by joining lines, packing statements or deleting useful comments.
  • Split an over-ceiling file in its own commit before adding to it.
  • Before moving code, check for guards pinned to a file path; a moved function can make such a guard go silently blind. Update the pin in the same commit.
  • A "use server" file keeps every exported action defined in it; move helpers to a sibling folder.
  • Exempt: generated files (OpenAPI types, frontend catalogues), a framework settings file, tests (in stack A).

Across the org. Stack B counts 300 non-blank, non-comment lines, tests and scripts included, enforced by the ESLint max-lines rule (error) and a unit test in Workspace, Events, Machines, Inventory, Bookings and Organization (the last runs tests/unit/file-length.test.ts). SpaceWorks: reviewed per change with a small published list of over-ceiling files; the React frontend has none. Android and iOS: not checked.

Part of Space Works product rules, published from docs/product-rules.md. Edit it there.