Understand the customer portal
The customer portal at web/portal.html is the shared workspace inside the production SABLE app. Customers and staff work from the same scoped records, while membership, role, project, and publication rules shape what each person can see or change.
Use the customer and staff guide for task steps. Use the customer portal API for request and response contracts.
Understand the two portal modes
The portal opens in one of two modes after SABLE verifies the signed-in person:
- Customer workspace: a customer sees authorized workspaces and can switch between them without entering the staff app
- Staff launcher: an admin or lead sees every client in their organization; a consultant sees only active or current projects assigned through
project_members
The live service uses one production Vercel app and one production Supabase project. client_memberships, organization scope, and project assignments isolate each workspace. A separate deployment per client is not part of the current production architecture.
Customize customer tabs
A lead or admin chooses which tabs a customer sees and which one they land on when entering their workspace. These controls live in Prepare workspace → Customer tabs & automation. The customer tabs are:
- This week: the customer's card home: short cards for Weekly updates, Recent meetings, Emails, Files, and Needs you (see This week cards)
- Overview: groups current work into Your actions, Our team’s actions, and Waiting on others; shows published changes since the previous recorded visit, the next milestone, contacts, and Ask SABLE
- Shared plan: shows customer-visible milestones, owners, target dates, commitments, blockers, and schedule notes
- Reviews: shows the current immutable released version of each deliverable, its discussion, decision status, due date, and receipt
- Requests: keeps the customer-visible thread, assignment, next-response owner, due date, attachments, status, and resolution receipt together
- Resources: lists released documents, meeting summaries and emails, with approved source links when available (an email never has one)
A client with no saved settings sees Overview, Shared plan, Reviews, Requests, and Resources (not This week) and lands on Overview — exactly today's portal.
Staff see all tabs except This week (shown only when enabled), with tabs hidden from the customer marked "Hidden from customer". The staff view is not an exact customer preview.
Hiding a tab changes what the customer sees, not what is released. No hidden tab protects sensitive material. Only released items ever reach the portal. Customer notifications for reviews, milestones, and requests are skipped when the matching tab (Reviews, Shared plan, Requests) is hidden. This includes posts to the customer's shared Slack channel for that category. Other notifications, and all staff notifications, are unaffected.
If Reviews is hidden, a deliverable release from Draft Workbench still completes, and the person releasing it sees a warning that the customer can't see or approve it until Reviews is turned on.
Search spans published updates, milestones, current reviews, requests, customer-visible conversation text, and released resources. Exact links use client, view, and id query parameters, so a notification can open the authorized record directly.
This week cards
Since 20 Oct 2026 (Lindsey and Shalonda), This week is a single page of short, collapsible cards instead of long scrolling sections. Each card's title is a button that opens or closes it; the page remembers each card's choice for that browser tab. The cards, in this order:
- Weekly updates: every published, customer-visible update in the viewer's project scope, newest first, one line each (title · date). Choosing a line opens that update's full text in place. Five lines show; Show more opens the rest (up to the 30 newest).
- Recent meetings: shared meetings with an expandable summary, and a recording link only when the lead opted in.
- Emails: emails a lead published (see Share emails), newest first, one line each (subject · sender name · date). Choosing a line opens the short summary, the names of the attachments shared with it (each with Download when SABLE holds the file) and Read full email, which opens the approved full email in a dialog (plain text, line breaks kept). An email published without a full email (every one before 23 Oct 2026) shows its summary only; a summary over 300 characters or 4 lines (older shares often hold the whole email) shows its first lines, with the rest behind Read full email.
- Files: important links, the key shared material, and the newest shared documents. A document SABLE holds the file for has Download. Upload files sits in the card header when uploads are allowed, and See all in Resources appears when the Resources tab is on.
- Needs you: the customer's open actions, shown only when something is waiting on them.
Leads and admins choose the cards per customer under Customer tabs & automation → Cards on
This week. All five are on by default, including for a customer with no saved settings
(portal_policies.this_week_cards). Emails joined the list on 20 Oct 2026: a customer whose cards
were saved before then keeps exactly those cards, and a lead turns Emails on for them. If the
code reaches production before the database allows the Emails card
(20261020170100_portal_this_week_emails_card.sql), a save that turns it on keeps every other
setting, leaves Emails off, and says so (receipt.cards_not_saved). The customer sees only the cards that are on; staff see every
card, with the ones turned off marked "Hidden from customer". Turning a card off changes what the
customer sees, not what is released.
Download shared documents
Since 20 Oct 2026, customers can download a shared document from the Files card, the Resources list, or the document's detail. The server checks the document is one the portal shows this viewer (the same rule as the Resources list: released, not Internal, not restricted, active, in one of the viewer's current projects, and not withdrawn) and returns a link that works for 60 seconds. View as customer can download too, and staff can download only what the customer can. A document without a stored file (for example, one shared only as a link) has no Download.
Share emails
Since 20 Oct 2026 (WATT customer-portal plan, steps 4b-1 and 4b-2), a lead or admin can share an ingested email with the customer. Nothing shares an email automatically: automatic meeting sharing never touches email, and only an explicit publication puts one on the portal.
- Where: in Prepare workspace, choose Emails as the source type and choose Publish to
portal. Admins can also publish from the Filing tab in Approvals (and
/inbound.html), on any filed email; the row then says On portal and offers Withdraw. - Which emails: filed on one of this customer's active, current projects, not dismissed, not Internal, and with nothing extracted from it marked Internal.
- The dialog: the title starts as the subject. Summary the client sees starts as a 2–4 sentence summary SABLE suggests from the new message (the start of the message when the model is unavailable). Full email starts as this message only, attachment text removed; ticking Include earlier messages in the thread appends the earlier thread under "Earlier messages". All are editable; an empty Full email shares the summary only (Kirk, 23 Oct 2026: long threads were unwieldy). A warning reads "Check the summary and the full email: signatures, phone numbers and forwarded headers will be shown to the client as written." The sender's display name is shown read-only; the customer never sees an address, cc or bcc. Attachments are listed in two groups: those publishing shares, and those that stay with the team (restricted, contract or SOW, or Internal). An email never carries a link.
- What the customer sees: the title, the summary, the approved full email behind Read full
email, the email's date, the sender's name and the shared attachments' names, on the Emails
card and in Resources (
20261023180000_portal_email_full_text.sql). One notice per email, not one per attachment. - Withdraw takes back the email and the attachments it shared.
- Automatic withdrawal: dismissing the email, removing it from its project, or moving it to another project withdraws it and the attachments it shared, recorded like a lead's withdrawal (an event and an audit row; no customer notice). After a move, publish it again from its new project if the customer should still see it. Marking the email Internal withdraws it too.
Share important links
Leads and admins add, edit, reorder, and archive important links for the customer workspace. Only http and https links are allowed. Links appear on the This week view in the Files card.
Let customers upload files
Since 20 Oct 2026, a customer can choose Upload files on Resources or in the This week Files card to add files to one of their current projects. When they have more than one project, they pick it. PDF, Word, Excel, PowerPoint, CSV, text, and image files up to 15 MB are accepted; macro-enabled files are not. The file lands in that project's Library, shows in Resources right away with no approval step (uploads are intentional, not AI), and leads get a portal notice.
Leads and admins turn this off per customer with Customers can upload files under Customer tabs & automation. It is on by default, including for a customer with no saved settings. The button never appears in View as customer, which is read-only.
Staff uploads follow the same rule. The project page's Upload files and the Library upload carry Show on client portal, ticked by default: the file shows to the customer as soon as its upload finishes (an upload that never finishes never shows). Untick it to keep the file Internal. Contracts and SOWs stay restricted and are never shown. Files that arrive as email attachments are not affected; they still wait for staff to share them.
The portal lists an uploaded file in Resources, where it can be downloaded like any shared document (see Download shared documents).
Auto-release client meetings
When turned on, meetings are shared automatically with the client. SABLE shares the meeting title and SABLE's summary. The recording link is shared only when Also include the recording link is also turned on (it is off by default). That link opens the provider's share page, for example Fathom, which may show the recording and its transcript depending on your Fathom sharing settings. SABLE itself never shows transcripts or internal meeting records in the portal.
Turning the setting on also shares eligible meetings from up to the backfill window before it was turned on (30 days by default). A meeting qualifies for auto-release only if it:
- Has a summary: the meeting's own (3–5,000 characters), or a short client summary SABLE wrote for it in the last 7 days (see below)
- Is not marked internal
- Has an attendee's email domain that matches a domain signal on that meeting's project (internal domains excluded: Indigo Trigger's own, SABLE's mail domains, the org's employee email domains, and others in the internal list)
- Has not been previously published or withdrawn by staff
Meetings outside the backfill window, or on projects without a matching client domain, are skipped. When the recording link is included, meetings without a recording link are skipped too; with it off, they are shared with their title and summary. Turning automatic sharing off also turns the recording link off. Changing the recording link setting affects only meetings shared afterward. If the admin or lead who last saved these settings loses admin/lead access, nothing is released. A meeting that was published or withdrawn is never re-released; a meeting marked Internal is not shared while it stays Internal.
Most meetings arrive without a summary SABLE can share. The note-taker's own summary (for example Fathom's) is usually long and written for the team. For those meetings, auto-release first writes a short client summary of 2–4 sentences, acting as the admin or lead who saved these settings. It works only from the note-taker's summary, never the transcript or SABLE's meeting record. It is told to leave out anything that reads as internal to the team, pricing and other commercial terms, staffing, opinions about people, and next steps the client has not agreed to. SABLE then discards the summary if it:
- Runs over 1,200 characters
- Contains an email address, including spelled-out or mixed forms such as "name (at) company (dot) com", "name (at) company.com" or "name@company dot com"
- Contains a phone number (a run of 10 or more digits that is not made up only of dates such as 2026-09-23 and years)
- Contains any percentage: a number followed by "%", or the words "percent", "per cent" or "pct", with or without a number (so "twenty percent" counts)
- Contains a currency symbol next to a number (such as "$500" or "300€"), an uppercase currency code next to a number (such as "EUR 300", "CAD 3000" or "5k USD"), or the words dollars, euros, pounds, bucks, cents, pence, rupees, yen or francs
- Contains an amount with a size word or suffix, such as "40k", "2.5m", "10mm", "3 million" or "1 billion"
- Contains a rate: "per" followed by hour, day, week, month, year, annum, seat, user, license, head or person; a slash rate such as "/hr" or "/mo"; or a number followed by "a" or "an" and a period, such as "40 a month" (but "two meetings a week" is allowed)
The checks are broad on purpose, so a harmless sentence such as "adoption rose 20%" is discarded too. Dates such as 2026-09-23, years, quarters such as Q4, and lowercase words that match currency codes (such as "try" or "cad") are allowed. Nobody reviews the summary before it is shared, so check auto-shared meetings in the portal and withdraw any you do not want the client to see.
SABLE stores the summary on the meeting, and the meeting is shared on that run. Only meetings from the last 30 days that otherwise qualify and have a note-taker summary get one. SABLE never replaces the meeting's own summary or a summary staff wrote. A summary it wrote itself is used for 7 days; after that, an unshared meeting gets a fresh one. Each run writes at most 10 summaries, at most 3 per client. Clients take turns going first each hour, so every client gets a turn. If writing fails, the meeting is tried again on the next hourly run, up to 3 attempts, then once a day. If the meeting cannot be summarized at all (for example, the summary was discarded), it waits a day before the next try. After 5 failed attempts SABLE stops trying; staff can still share the meeting with their own summary.
Auto-release runs on an hourly schedule via the Trigger task sable.portal.auto-release. Additionally, when the auto-release setting is first turned on, the task sable.portal.auto-release.client runs immediately to backfill recent meetings.
Preview as staff
Admins and leads can open a client's portal to check what has been shared. Staff see the tab list described above, with This week shown only when enabled, and tabs hidden from the customer are marked "Hidden from customer". The staff view is not an exact customer preview; use View as customer for that.
View as customer
View as customer in a client's portal header shows the workspace exactly as a customer contact who can approve work sees it — the broadest customer role, so nothing a customer could see is missing:
- Only material shared with the customer appears. Internal conversation notes, unshared milestones, and staff tools (Prepare workspace, invites, access, plan editing, request management) are hidden, and only the tabs enabled for the customer show.
- Actions are turned off. Forms and buttons are shown as the customer sees them but cannot send anything, and the page refuses any change while the preview is open. Your own notifications and preferences are hidden.
- The preview keeps its own page state: your drafts and your "since your last visit" time are not carried into it, and opening it does not count as your visit.
- A consultant who is assigned to only some of the client's projects sees only those projects, and the banner says so.
Choose Exit customer view in the banner to return to the staff view. The preview is GET /api/client-portal?client_id=…&preview=customer. Only staff on that client get a preview; a customer who opens a preview link sees their own workspace, which is already the customer view.
Understand the workspace response
GET /api/client-portal returns the current portal contract:
viewer,client,clients, andworkspacesprojects,contacts, andcapabilitiessummary,queues, andchangesmilestones,reviews,requests, andresourcesmilestone_optionsandpage
Customers who omit client_id receive their first authorized workspace plus metadata for every workspace they may switch to. Staff who omit client_id receive the launcher. The response never includes raw transcripts, raw document bodies, storage paths, internal request events, unrestricted documents, or internal operating tables.
Apply membership and role boundaries
Portal permissions come from verified server records:
| Role | Read scope | Portal actions |
|---|---|---|
client_viewer | Clients in client_memberships | Create and reply to requests, download shared files, and comment on reviews |
client_reviewer | Clients in client_memberships | Viewer actions plus approve or request changes on the current released version |
| Consultant | Active or current assigned projects | Reply to and manage requests and milestones inside exact project scope |
| Admin or lead | Every client in the viewer’s organization | Preview workspaces, manage access, release deliverables, and manage portal records |
The API derives the person and organization from the Supabase session. Browser fields cannot grant another role, organization, client, or project.
Revoking a membership removes the authorization grant, revokes active invitations, and purges that member’s pending inbox and delivery rows. A still-valid Auth token does not preserve portal access.
Publish only approved customer material
The portal applies publication gates in addition to access scope:
- Updates require
visible_to_client = trueandstatus = 'published' - Deliverables require
visible_to_client = trueand a validcurrent_version_id - Documents and meeting summaries require an allowed active project and an explicit curated publication; a document can also inherit visibility from an active immutable released deliverable
- Milestones require
portal_visible = true - Request events require client visibility; internal notes remain staff-only
- Attachments appear only after they reach
readyand bind to a published request event
Releasing a deliverable creates a new immutable deliverable_versions row and review state. Comments and decisions bind to that version. Releasing changed content creates another version, marks the earlier state superseded, and prevents the earlier approval from carrying forward. Stale revisions and decisions against an old version return HTTP 409.
Keep request work durable
Requests use the states open, in_review, waiting_on_client, changes_requested, resolved, and closed. Staff manage the assignee, next-response side, response date, and status. Setting resolved requires a resolution summary, which becomes the visible resolution receipt.
Every request mutation accepts an idempotency key and, after creation, an expected revision. The browser preserves drafts and the exact pending payload after uncertain failures. Retrying checks the same operation instead of duplicating it. A conflicting completed mutation returns HTTP 409 and the draft remains available.
Files use the private portal-request-attachments bucket. Each request may retain 50 nondeleted files, and one reply may bind up to 20 files. A reply may contain text, attachments, or both. Supported types are PDF, PNG, JPEG, WebP, DOCX, and plain text, with a 10 MiB limit per file.
The server validates metadata and uploaded bytes before marking a draft ready. Completed drafts remain staged across partial failures and become visible only when the reply commits. Downloads use signed URLs that expire after 60 seconds. The cleanup worker claims unpublished pending, ready, quarantined, or retryable cleanup rows after 24 hours; expired deleting leases can be reclaimed.
Prepare customer content deliberately
The staff publication inventory shows available and shared documents, meetings, release-ready drafts, memberships, milestones, requests, deliverables, and published updates. Its readiness totals come from the same database rules as the launcher.
Launcher readiness includes exact shared_resources, published_updates, deliverables, milestones, requests, and membership_count fields. shared_content_count combines the published customer categories. ready becomes true only when the current readiness conditions pass.
Admins and leads can publish a curated document or meeting title and summary, choose whether to include the approved source link, withdraw an explicit share, or publish a direct customer update. Consultants can browse candidates in their exact assigned project scope but cannot publish.
Publishing stores a curated metadata snapshot rather than exposing source content. The customer receives no document body or meeting transcript. Withdrawing an explicit share hides it unless an active released deliverable still links the document.
A publication is bound to the source's client and project at the time of the decision. Moving a published source to another client or project suppresses that publication and the preparation inventory reports the scope mismatch. Withdraw a share before intentionally reclassifying its source; after a mismatch, correct the source scope before attempting another publication action.
Deliver notifications without widening access
Request, review, milestone, release, and access mutations write a transactional outbox event with the domain change. The worker resolves recipients from current server relationships, materializes one inbox row per person, and rechecks authorization before email delivery.
In-app notifications stay enabled. Each person can enable email, choose immediate, daily, weekly, or off, select activity categories, set a time zone, and define quiet hours. Access messages remain essential. Existing members start with email disabled, and the migration creates no notification backfill.
Delivery rows use leases, retries, and stable idempotency keys. Digest batches persist one batch key before the provider call and complete as a unit. A worker restart can reclaim expired leases without splitting or duplicating a digest.
Ground workspace answers in released evidence
Ask SABLE sends only authorized, released workspace evidence to the model. Eligible evidence includes the current released review, shared milestones, published updates, customer-visible request events, released documents, and released meeting summaries.
The server resolves citations from the evidence it supplied. It rejects model-generated or invalid source links. If the released evidence does not support an answer, the response reports that limitation and returns no invented citation. The assistant has no mutation tools.
Know the current limits
Portal review records an immutable, auditable version decision. It is not a formal electronic-signature packet. Billing and contract-signature workflows remain outside this portal surface.