The archive
01 / SA· Live·Live in production

Sonic Artistes Agency App

Full-stack musician management platform for cruise line entertainment. Document compliance, scheduling, chat, repertoire management, support tickets and multi-sided portals — all in one.

15+Modules
400+Musicians
3Portals

Overview

The Sonic Artistes Agency App is bespoke software that runs an entire cruise-line entertainment agency. It manages 400+ musicians, every contract and ship rotation, document compliance across multiple cruise operators, and three distinct portals — one each for the agency's staff, the performers, and the cruise-line clients they supply. It has been live in production for six months.

This is the kind of system most businesses are quoted a year and six figures to build. We built it as a small team working hand-in-hand with AI, and it is not a prototype or a thin MVP. It is a hardened, multi-tenant platform with row-level security, realtime everything, a 51-type notification engine, and a full GDPR data-request module that most SaaS companies never get round to writing. We are showing it here because it is the honest ceiling of what we build — and it is already running every day.

Three portals, one source of truth

Staff, musicians and clients each get their own purpose-built portal, governed by a four-role access model enforced at the database layer. The agency's schedulers run the whole operation from the admin side; musicians get a clean self-service space for their assignments, documents and check-ins; and cruise-line clients see only the performers shared with them, down to individual document level. Nobody sees a row they shouldn't — and that boundary is enforced in the database, not just the interface.

Contracts, scheduling and compliance

The assignment builder lets schedulers create and manage contracts with smart musician recommendations based on role and availability, automated status transitions, offer letters and extensions, and a bespoke rotation pipeline for the cruise operator's approval process. Document compliance is fully dynamic: document types are configured in-app with their own forms, expiry rules and verification workflows, grouped into compliance phases, with branded PDFs generated automatically on completion. Expiry reminders chase the right people at the right time and stop the moment a document is renewed.

Communication and community

A complete realtime messaging suite covers direct messages, group chats and broadcasts, with read receipts, reactions, reply-to quoting, @mentions, pinning and a shared inbox for the support team. Gated community Spaces give cohorts of musicians their own rooms for posts, comments and shared resources, with seen-by tracking throughout. A separate integration wires the platform into The Hub, the agency's wider musician community.

The engines underneath

Beneath the features sit the systems that make a platform feel finished. A 51-type notification engine with per-role email, digest and push delivery, every channel admin-configurable without touching code. A database-driven email system with merge-tag templates and a full audit log. Configurable workflow checklists with step dependencies and automations. An analytics module with fourteen reporting views, six dashboards and a saved-query builder that exports to PDF and CSV. Support tickets, RAG-rated HR touchpoints across the recruitment lifecycle, repertoire management with per-song proficiency, bookmarks, announcements — the breadth a real operations tool needs and a demo never has.

Built to last

The whole thing runs on self-hosted Supabase, strictly typed end to end, with a disciplined security model and a single consistent design system across every portal. It is the sort of platform you would normally hand to an agency, write a £100k cheque for, and wait the best part of a year to see. We would rather just show you it working — and talk about what we could build for you next.

Screenshots

10 images

Build log

74 entries

  1. The Assignment column on the contracts tab is now a link straight to the assignment

  2. Self-hosted audio upload now works in every editor

  3. Audio upload switched on for the Knowledge Base editor

  4. Space posts can now embed third-party media

  5. Space posts get the rich-content treatment: colour, highlight and callout cards

  6. The dashboard's stale attention card is now a live Contract Check-ins feed

  7. Repertoire tables stop clipping rows on mobile portal pages

  8. Notes get the rich-content editor and a visible comment toolbar

  9. The whole UI moves to self-hosted Mona Sans

  10. Pulled the broken 'Upload audio' menu item while we tracked down the cause

  11. Audio uploads now flow through one shared chokepoint, so every entry point behaves the same

  12. MP3s upload cleanly into the audio node and embeds stop letterboxing

  13. Rich-content gains media embeds and self-hosted audio

  14. Inline images get loading skeletons and size/alignment presets

  15. Editor polish: an autosave timestamp, sticky toolbar, reader-to-edit jump and balanced search

  16. The rich-text editor gains headings, alignment, callouts and checkboxes

  17. Spaces membership now follows the audience — all active musicians — with guardrails on announcement emails

  18. The portal notifications card shows only unread items and disappears when there are none

  19. The musician's dashboard gets a refresh: Knowledge Base surfaced, cards reordered, a More menu

  20. Tidied the global rail into Operations, Workspaces and a pinned Settings

  21. Clients moves into its own workspace rail

  22. The Hub moves into its own workspace rail

  23. Analytics moves into its own workspace rail

  24. Repertoire moves into its own workspace rail

  25. Workspace rails get the brand and user footer back, and auto-close on mobile

  26. The Knowledge Base reader gets a role-aware 'Back to dashboard' escape hatch

  27. Admin musician profiles get a Learning tab

  28. Authors can now decide per image whether readers may download it

  29. Readers get an in-app image lightbox and PDF preview

  30. Knowledge Base Settings lands, with an access audit and icon polish

  31. One shared Knowledge Base reader now serves both musicians and staff

  32. The Knowledge Base module begins: database foundation and admin workspace

  33. Lesson attachments now render as full-width download cards

  34. Course player controls make sense again: contents on the left, close on the right

  35. Course content gains colour, highlight and cards, and the link dialog is fixed

  36. Courses get an overview page, cover images and rich cards

  37. Musicians get a focused Learning player, surfaced across the dashboard and nav

  38. The admin Courses workspace lands: list, builder, tracking and settings

  39. The Courses/Learning module begins with its database foundation and types

  40. Broke the 715-line AppSidebar into nav groups, down to 478

  41. Settings moves into its own workspace

  42. A new WorkspaceShell layout primitive lays the groundwork for workspaces

  43. Notifications lose a redundant meta row and tighten up their spacing

  44. Contracts gain a type — onboarding vs land rehearsal — and rehearsals drop out of the analytics

  45. Analytics gains a contract-length distribution and a corrected average-length KPI

  46. Analytics now tracks musician return rate and re-engagement

  47. Chat notifications stop opening the wrong thread and leaking messages across channels

  48. Squashed more hydration gremlins: an SSR viewport hint and mount-gated dialog teleports

  49. Hydration follow-ups: timezone greeting, plugin promise idiom, sidebar default and AppHeader gating

  50. Eliminated the admin hydration mismatches, and retired the sidebar Cmd+B shortcut

  51. Tab counters had grown eight different shapes. Now there is one.

    The little number chips that sit beside tab labels — “All 24”, “Unread 3” — had been built eight slightly different ways across the app as features piled up, and they had drifted into eight slightly different sizes. A couple were tall enough to nudge their tab out of line with its neighbours. I pulled them all onto one shared chip with a fixed height and tightened the tabs themselves into a cleaner, compact pill, so every tab strip in the app now lines up and reads the same. The sort of thing nobody notices when it is right and everybody feels when it is wrong.

  52. Shared repertoire did not look read-only — so people kept trying to edit it.

    When a musician shares their repertoire ratings with someone, the viewer gets a read-only copy. The trouble was it did not look read-only — the rating dots were identical to the editable ones, so viewers kept tapping them and waiting for something to happen. I made the view-only state unmistakable: static dots, no hover affordances, and a clear marker that this is someone else’s repertoire. A small change that removes a confusing dead end.

  53. Bookmarks finally read like a proper list.

    Bookmarks shipped recently as a way to save any musician, contract, document or note for later, but the list itself was a bit scrappy — uneven rows, no way to sort, and the note you had jotted squeezed for space. I rebuilt it as a proper table: consistent rows, a note column that flexes to the room available, and sortable headers so you can order by type or date. Now it works the way the “save for later” surface was always meant to.

  54. Musicians were getting the same expiry reminder every other day. Now it is once.

    Our document-expiry reminders had a subtle bug. Instead of one nudge when a document entered its warning window, musicians were getting the same reminder every couple of days for the whole window — dozens of emails for a single passport over its renewal period. The cause was a timing guard sized for the wrong interval. I rewrote it so a reminder fires once when a document reaches its threshold and then stays quiet until the document is actually renewed. Far fewer emails, and the ones that do land mean something.

  55. Announcements: one banner, everyone sees it.

    Added announcements: a dismissable banner the team can put across the top of any of the three portals — staff, musicians or clients — to flag a maintenance window, a policy change, or anything everyone needs to see at once. Each one targets whichever audiences it is meant for, sits inside a date window so it shows and hides itself, and a reader can dismiss it once and never see it again. The kind of small, boring tool you reach for constantly the moment it exists.

  56. A data request used to mean hunting down files by hand. Now it is one button.

    Under GDPR anyone can ask for a copy of every piece of data you hold on them, and you have a month to deliver it. That used to mean an admin manually tracking down a musician’s documents and exporting their records one by one. I wired the whole thing into a single action: the platform now gathers the person’s held documents and a full export of their records into one bundle, fronted by a branded cover letter that lists exactly what is included. A legal obligation most companies quietly dread, turned into a button.

  57. The analytics query builder could only filter one thing at a time. Now it stacks.

    The analytics query builder lets managers slice the roster — by position, status, nationality, document status and so on — but every filter could only hold a single value, so a question as simple as “pianists or guitarists” was impossible. I made every filter multi-select: pick several values in one field and it matches any of them, combine different fields and they narrow together. While I was in there I added saved queries, so a useful slice can be named, kept and shared instead of rebuilt from scratch each time. It turns the builder from a one-shot lookup into something you actually live in.

  58. Expiry reminders were chasing musicians who had already left.

    Two notification cleanups landed together. First, the daily document-expiry reminder was emailing, and pestering managers about, musicians who are inactive, archived or no longer with us; scoping it to active musicians only cut the daily noise roughly in half. Second, I enriched the offer- and extension-letter notifications so they carry the full set of details any email template might want, rather than only what today’s template happens to use. That second one is purely preventive: it means wiring up a richer email later will not render with half the merge fields blank, which is exactly the kind of silent breakage that looks awful in front of a musician.

  59. The workflow builder could never actually save a step.

    It turned out the workflow template builder had never been driven from the UI: the existing steps were all seeded directly, and the save path was writing to fields that did not exist, so every save silently failed. Fixing it was a peel-the-onion exercise because the data layer only complains about the first unknown field at a time, so I had to diagnose the whole payload at once rather than one error at a time. Then I found the runtime side was dropping step dependencies and skipping due-date calculation for every date type but one, so I rebuilt provisioning to carry blocks, dependencies and all the due-date variants through properly. A feature that looked finished on the surface and was quietly broken from end to end.

  60. One document upload was firing two emails.

    A musician submitting a document in a single action was somehow triggering both an uploaded and a submitted notification to every manager, so everyone got two emails for one event. The cause was subtle: completing the form on upload crosses two state thresholds in the same write, and the notification logic treated each crossing as its own event. I added a guard so when both transitions happen together only the meaningful one fires, while the genuine upload-now-finish-later case still sends both. While I was in there I also fixed the wording so when an admin acts on a musician’s behalf the message says so, instead of pretending the musician did it themselves.

  61. Two badge systems doing the same job. Now there is one.

    The app had quietly grown two parallel ways of rendering status pills, an older custom badge style and the newer shared component, and they had drifted just enough to look inconsistent next to each other. Worse, the hand-rolled ones kept leaking raw values like not_verified straight into the UI instead of a clean Not Verified. I collapsed everything into a single badge system so every status pill goes through the same label-mapping path. Tedious chasing down every last usage, but now there is exactly one place to change how a badge looks or reads.

  62. I kept losing the musician I was looking at five minutes ago.

    Managers bounce between musicians, contracts, documents and notes all day, and there was no quick way to pin the handful you are actively working on. So I added personal bookmarks: a toggle on each detail page, a count badge in the top bar, and a dedicated page with per-type tabs and an optional note on each one. The fiddly bit was making a freshly added bookmark show its full details immediately instead of a blank placeholder until refresh; an optimistic insert only hands you the bare row, so I had to re-fetch the enriched version right after saving. Bookmarks are strictly private too, so even admins cannot see anyone else’s.

  63. Recruiters were tracking musician follow-through in their heads. Now it has a colour.

    Our recruiters judge musicians across the whole hiring journey: did they reply quickly, show up on time for the video call, get their compliance paperwork in. All of that lived in people’s heads or scattered notes. So I built a green/amber/red rating across seven lifecycle touchpoints, with the worst rating bubbling up as an at-a-glance overall on the musician profile and the list. The one rule I cared about was that you cannot mark someone amber or red without writing why, enforced both in the form and at the data layer so a rushed rating can never skip the context.

  64. You couldn’t revoke a document mid-review. Now you can.

    A document sitting in review was effectively frozen — if a manager uploaded the wrong file or realised a mistake, the only option was to reject it and start over, which created noise in the audit trail and an unnecessary rejection notification. I extended the revoke action to cover in-review documents, with a status-aware dialog that shows “Reset Review” instead of the standard revoke label so the intent is clear. The behaviour is deliberately different: revoking an in-review doc preserves any review notes that were added during the review pass, whereas revoking a verified or rejected doc wipes them — because the use cases are different. I also added an inline notes editor to the admin document sidebar so managers can annotate a document directly without hunting for a separate notes panel, and that same note surfaces as read-only on the musician portal so they can see any context left for them. The activity timeline view was rebuilt to include a distinct “Review Reset” entry, which matters because the database trigger that fires on status changes would otherwise swallow the revoke action and lose the audit event.

  65. Documents in review can now be revoked, and verification notes are editable inline

    Two small but meaningful improvements to the document verification workflow. Previously, admins could only revoke documents that were already verified or rejected — if a document had just entered the review queue and something was wrong, you were stuck. Now revocation works on in-review documents too, with a status-aware dialog that shows “Reset Review” instead of the generic revoke label so the intent is clear. We also added an inline notes field directly on the document sidebar — admins can attach a note without going through the full notes system, the note persists in the activity trail even through status changes, and musicians see it in read-only form on their portal. It’s the kind of thing where you notice the absence more than the presence once it’s there.

  66. The musician profile page was 1,200 lines. The page itself is now under 100.

    The admin musician detail page had grown to over 1,200 lines of mixed concerns — data loading, UI state, tab logic, Heartbeat provisioning, compliance calculations, all tangled together. We extracted everything into a dedicated useMusicianPageOrchestrator composable, which now owns the full data lifecycle: loading contracts, documents, notes, compliance maps, and phase progress in one coordinated place. After the relocation was stable, we went back and fixed four reactivity violations that had been lurking in the remaining code — places where state was being mutated outside of Vue’s reactive system, which could cause the UI to show stale data after a status change or provisioning action. The page file delegates to the orchestrator and handles only the UI shell.

  67. The musicians detail page was 1,400 lines. Now it’s 600.

    The admin musician detail page had accumulated everything in one file — data fetching, form state, hub wiring, repertoire, email composition, availability. Extracted all of it into a dedicated orchestrator composable. The template is byte-identical; nothing visible changed. One near-miss during cleanup: a grep for an unused import matched component names that happened to contain the same string, and I nearly dropped an icon still being used in the template. Code review caught it.

  68. Built GDPR data request management from scratch

    The app needed a proper way to handle data subject requests — access, erasure, rectification, portability — under GDPR/UK-GDPR. Built it admin-only: log incoming requests, verify identity, generate a data export, manage correspondence across multiple channels, and track the 30-day compliance clock with automated overdue alerts. Erasure requires a type-to-confirm gate before anything destructive runs. Took a spec-first approach — two rounds of fresh-eye review before a line of code was written, because the scope was wide enough that finding a design flaw mid-build would have been expensive.

  69. Client portal documents now have a preview

    Clicking the preview icon on a document row in the client portal opens it inline without navigating away. Dropped some stale inline form data that had been cluttering the row metadata while I was in there.

  70. Let users turn off DMs

    Managers and admins can now set their DM availability to ‘internal team only’ or ‘not accepting DMs’. It’s asymmetric — restrictions are based on the recipient’s setting and the sender’s role, so the same block applies differently depending on who’s trying to send. The UI gates the interaction, but there’s server-side enforcement as a backstop in case of stale caches or anything bypassing the front end. One thing I’d do differently: the role comparison was case-sensitive in a way that caused a quiet failure in one path — server enforcement was catching it silently before we spotted it.

  71. Added ‘Seen by’ to Space posts

    Space posts now track who’s read them, mirroring the same approach already used in chat. There’s a compact indicator on post cards and a full popover with reader names on the detail view. The read happens silently when posts load. One deliberate call: authors are always excluded from their own ‘Seen by’ list — you obviously read what you just wrote.

  72. Documents can now be revoked while in review

    Previously a document stuck in ‘in review’ state couldn’t be revoked — you had to wait for the review to complete first. Closed that gap. Also added editable inline notes to document rows while I was in there.

  73. Decided against merging the marketing site and the app

    Was tempted to unify the marketing site and the Sonic Artistes app under one Nuxt SSR deployment. Thought about it for an hour — it’s a bad idea. The marketing site is fully static (Cloudflare cache hits 100%); the app is heavy SSR behind auth. Merging them costs the static cache and buys nothing. They stay apart.

  74. Pulled document compliance logic into a composable

    The compliance panel had grown a fourth “is this document valid” branch and the component was creaking. Extracted useDocumentStatus() — the same logic now drives the list, the detail modal, and the cruise-line export. No behaviour change, but the unit tests finally cover the whole thing.