← Design · c137

ports.md

c137 Phase 2: the ports and the citizen's controls

Written: 2026-11-09. Reader: Mark at the review stop; c131's executor (the OS plumbing, the tray, the inbox store); c134's executor (the operator's side); Tyler at G2 (Q5). Rules it carries: R3 the citizen controls delivery, the overseer controls content · R4 nothing is alarming · R6 honest and bounded · R7 use the OS. What a message is comes from bones.md; which messages exist from inventory.md. This document is where they go and what the person at the keyboard can do about it. The lab mock that shows it is §4.

1. The ports

PortCarriesControlled byShows
Inbox (native app)every message, kept with its state and threadthe app; always onthe record: open threads first, ended ones under a fold; the honesty line
Traya count and the one line that mattersthe citizen (on · off)the count of open messages that wait for the person; the newest open subject
OS notificationone interruption per message, at deliverythe citizen, per type, with quiet hours; the OS's own permission and Focus; required passestitle, one sentence, the sender; opens the message
Web app (control plane)the operator's inbox and send; the pages messages link tothe overseerthe same record from the other side
Email, Slacknothing in V1later, through the plane's integrations (c134 D7), operator's inbox first—

1.1 Inbox

  • One row per thread; a thread shows its root's subject, the sender label, the time of the newest activity, the state pill, and the count of replies. Opening a row shows the card (bones.md §1) and the thread under it.
  • Open threads first, newest activity on top; ended ones (acknowledged, done, expired) under a fold labelled with their count. Nothing is deleted by the person; the record is a record (R5, R6).
  • The honesty line. When the settings held anything back, the inbox says so at the top, in one sentence and without blame: "3 notices went to your inbox quietly since 18:00." or "1 notice from the policy Secrets in prompts is muted until tomorrow 09:00." The line names the setting that did it and links to it. Control never erases the record.
  • The empty state carries the welcome the inventory dropped as a type (row 23): "Your inbox holds what HQ sends this machine. Nothing yet." In local mode: "Your inbox holds what qcontrol on this machine reports. Nothing yet."
  • No search, no folders, no flags in V1 (the five questions: not necessary for a record that holds tens of items a year).

1.2 Tray

  • The count: open messages that wait for the person: new, and requires_ack not yet acknowledged. A required message holds its place in the count whatever the settings say. Ended messages never count.
  • The one line: the newest open message's subject, under the sensor's own state line (c131 Phase 3 owns the tray). When nothing is open the line is absent: invisible when working.
  • Control: on · off, one switch on the settings page ("Show messages in the tray"). Off hides the line and the count badge; the inbox still fills.
  • A message with expires_at surfaces once more in the one line the day before it expires; never a second notification (bones §5).

1.3 OS notification

The platform's own centre, through the kit (show_system_notification; c130 platform.md §2), and nothing of our own drawn on top. What the app adds is the layer in front: per type, per policy, quiet hours, the one override.

  • One per message, at delivery, when the type's setting is notify (or the message is required). Never repeated.
  • Content: title = subject; body = body.happened (the first sentence) and the sender label; subtitle (where the OS has one) = why. Clicking opens the message in the app, which moves it to seen.
  • Actions on the notification: the primary action and Open, where the OS allows buttons; otherwise click-to-open only. Acknowledge is never a notification button: acknowledging means having read the card.
  • Quiet hours and Focus: the OS's Focus (macOS), Focus Assist (Windows) and Do Not Disturb (Linux desktops) are honoured as the OS applies them; the app does not fight them (R7). The app's own quiet hours (§2) are a second gate in front of the OS: inside them the app posts nothing, except a required message.
  • required in the OS's terms: the message asks the OS for the interruption level that breaks through Focus (macOS time-sensitive, which needs an entitlement; Windows Focus Assist priority; the freedesktop urgency: critical hint), when the build has that entitlement. Without it, required still posts one ordinary notification and takes its place in the tray and the inbox; the settings page says which the build can do. Critical alerts (macOS, Apple-approved, sound through mute) are never requested.
  • Per platform, what c131's spike must confirm (c130 platform.md §2 and "What the spike must test"):
macOSWindowsLinux
Delivers froman installed, signed .app bundle only; the app identity set earlyan installed app with a Start-menu shortcut carrying an AppUserModelID; never from an elevated processa session D-Bus and a notification daemon; tray-less GNOME still notifies
Permissionthe OS asks once at first post; provisional delivers quietly to the centre with no prompt (a candidate for the first run: nothing in anyone's face until they allow it)none beyond the shortcut; the person's Settings decidethe daemon's
Focus / quietFocus; time-sensitive needs an entitlementFocus Assist; priority listDND is per desktop; urgency hint
Buttonsyes, by categoryneeds a COM activator; V1: click opens the appdaemon-dependent; V1: click opens the app
Replace / retractby tagby tagthe kit's adapter neither retracts nor replaces
Delivery resultnone: the call returns nothing; the app records "posted", never "delivered" (R6)samesame
  • When the OS has notifications off for the app, the settings page says so in one line ("macOS has notifications for this app turned off. Required messages reach the inbox and the tray only.") with the OS setting linked. The app never asks twice.

1.4 Web app

The operator's inbox and send side (§3) and the pages that open-page actions reach: a finding, a policy, a training, a grant, the endpoint. Opened from the native app in the browser, signed in through the plane connection (c131 D13). The same record, the other side.

1.5 Email and Slack

Out of V1 for the citizen. Later, through the plane's integrations (c134 D7), and for the operator's inbox first (a reply from an employee should reach IT where IT already looks). The OS is the citizen's only interruptive channel until a customer asks otherwise (plan Q4).

2. The citizen's controls

One settings page in the native app, titled Messages. Everything on it is the person's; nothing on it changes the record.

2.1 Per type

Each type has one control with four positions, off · inbox · tray · notify, with the inventory's defaults. The person may set any type lower than its default, including enforcement notices to inbox only (R3: the citizen controls delivery). The one exception is written beside the control, not hidden in a dialog.

TypeDefaultNote on the page
Enforcement noticesnotify"A required notice still arrives once, whatever this is set to."
Notices from HQinbox
Connection confirmationstray
Censures from HQnotify"Censures are required and must be acknowledged."
Responsibilitiestray"You will be asked to acknowledge."
Trainingstray
Requests for diagnosticstray"Nothing is sent until you review it."
Sensor statetray"Only when something stays off or degraded."
Replies from HQtray

2.2 Quiet hours and the OS's Focus

  • The OS's Focus is always honoured, as the OS applies it; there is no switch for it here (R7).
  • Quiet hours are the app's own gate in front of the OS: off by default (the OS already has Focus), with one preset ("Outside working hours": 18:00 to 08:00 and weekends, in this machine's zone) and custom hours. Inside them, no OS notification; messages go to the inbox and the tray as their setting says.
  • No digest notification afterwards. The inbox's honesty line says what quiet hours held back; the person sees it when they look, which is the point.

2.3 Mute

  • From a card's menu: "Mute this policy's notices for today" (automatic messages; mute_key: policy:<id>) or "Mute this thread for today" (everything else; mute_key: thread:<id>). A mute lasts until 09:00 the next day in this machine's zone and lowers the muted messages to inbox.
  • The settings page lists active mutes with their end and a Lift.
  • required ignores mutes; the honesty line says so when it happens ("1 required notice came through a mute").

2.4 The single required override, and its count

Written on the page in two sentences, under its own heading: "HQ can mark a message required. A required message reaches you once even during quiet hours or with a type turned off, must be acknowledged, and is counted here." Then the count: "HQ has sent this machine 2 required messages this month." with the two subjects listed. When the plane's soft ceiling was passed, the card carries HQ's reason line (bones §6). The person cannot turn required off; the count is what they get in return (R3, honesty in both directions).

2.5 "Why am I seeing this"

On every card, one line under the body, in grey: the policy or the person that caused the message, fixed at send time (why). On the notification it is the subtitle where the OS has one. Never a dialog, never a link to a help page.

2.6 Nothing hidden

  • The honesty line in the inbox (§1.1) names what the settings held back and links to the setting.
  • Settings never delete or hide a message from the inbox; off means no tray and no notification, and the inbox still fills.
  • The settings page's last section, What this app records, says in plain words what leaves the machine: that a message was delivered, opened (only where an acknowledgement is required), acknowledged, done or expired; never how long it was read or anything else (R6).

Considered and left to the OS

A "pause everything for an hour" switch: the OS's Focus does it, and a second switch would mean two places to look (R7, the five questions: not necessary).

3. The operator's send side (c134's shell)

Proposed place: Admin › Messages (c134 decides the rail). Two views in one page: the inbox and Send.

3.1 The operator's inbox

  • Threads that wait for an answer first (a contact-it, a reply from the person, a diagnostics package), then everything sent, newest first.
  • A sent message shows its states per recipient, as counts for a batch: sent 42 · seen 30 · acknowledged 12 · expired 2, with the people behind each number one click away (the audit log, R6). seen is shown only for messages that require acknowledgement.
  • Recall on anything not yet ended: the card on every machine says "HQ withdrew this message"; the record keeps it (bones §5).
  • The honesty counter at the top: "Your plane has sent 2 required messages this month. The people see this number."

3.2 Send

FieldBehaviour
Typethe sendable ones: a notice from HQ, a censure, a responsibility, a training, a request for diagnostics. Enforcement notices come from policies, confirmations from the plane; they are not written here
Toa person, an endpoint, a team, everyone; the count of machines it will reach, shown before Send
Subject60 characters, with a counter
The three sentencesthree fields with their prompts (what happened · why it matters · what to do next), 200 characters each, with counters; a censure's are HQ's words and the form says they are shown as written
Actionsup to three, from the type's allowed kinds; the primary first
Severityinfo · attention; required only where the type allows it, and then the form shows the fleet's count this month and the highest count among the chosen recipients, and asks for a reason line above the soft ceiling
Expiresthe type's default, or a date (a notice about Monday expires Monday)
Previewthe card as the person will see it, and the OS notification as it will read; the voice lint's findings inline, on the three sentences
Sendone button; no drafts in V1; Recall afterwards from the inbox

3.3 Plane settings (three)

messages.sender_label (default "HQ") · messages.required_on_notices (default off) · messages.required_soft_limit (default 3 per recipient per month). Under Admin, beside the other org settings.

3.4 Audit

message.sent, message.acknowledged, message.done, message.expired, message.recalled; message.seen only for messages that require acknowledgement. Nothing about dwell or read time (R6). c133's proof of correction reads message.done on a training assignment.

4. The lab mock

Built in the phase-4 lab as design mocks (D8: the web side in the lab; the native side here only as a web rendering until c132 decides the medium, and each page says so).

PageRouteWhat it shows
Message bones/pages/phase4/messaging-bonesthe message component over the ten examples and the two operator-side ones, across the five states (new · seen · acknowledged · done · expired); the OS notification and the tray line for the same messages; the three-sentence rule visible on every card
Messages: the person's inbox and settings/pages/phase4/messaging-inboxa web rendering standing in for the native app: the inbox with the honesty line, open threads and the fold; the tray; the settings page (per type, quiet hours, mutes, the required count, what this app records)
Messages: the operator's side/pages/phase4/mock/messagesin the lab's qdash shell (not on the rail yet; c134 decides): the operator's inbox with states per recipient and Recall, the honesty counter, and Send with the preview and the voice lint

The three pages share one state for the visit (useMessagingMock): a message sent from the operator's page appears in the person's inbox; an acknowledgement there shows on the operator's side. The fixture is app/data/messaging-fixture.ts, written from bones.md §10. Layer components first (UxPageTitle, UxTabGroup, UxFilterGroup, UxPill, UxToggle, UxButton, UxPopover, DataTable); the card, the notification and the tray line are design-local (app/components/messaging/), candidates for the layer once the native side settles.

5. Questions the mock raised

  1. Enforcement notices set to inbox only. The person may lower enforcement notices to inbox (R3). Peterson's ask was that the person be told; is "told, in the inbox, when they look" enough, or should enforcement notices have a floor of tray? Default here: the person decides; the tray count is cheap and most will leave it.
  2. Provisional delivery on macOS as the first run. Posting quietly to the notification centre with no permission prompt until the person opens one is the least noisy start and matches "invisible when working"; it also means the first required message may arrive silently if the person never allowed alerts. Accept, or prompt at Connect?
  3. The required entitlement. Breaking through Focus on macOS needs the time-sensitive entitlement on a signed build; c131's spike finds out whether we get it. Without it required is one ordinary notification plus the tray. Is that acceptable for V1?
  4. Acknowledge from the notification. Kept off (acknowledging means having read the card). Mark may want it for censures on Windows, where a click already opens the app.
  5. The sender label per message. "HQ" is one label per plane. A customer with Security and IT as different senders would want two; bones keeps one string plus the operator's name. Enough?
  6. The operator's inbox place. Admin › Messages is proposed; the replies are closer to Findings in spirit. c134 decides with the rail.
  7. Local mode's Contact IT. A mailto from qcontrol's configuration (it_contact); no plane means no thread. Is a one-way path acceptable until the machine connects?
  8. Inbox order. Newest activity on top (bones §5) puts a three-day-old required censure, still unacknowledged, below yesterday's seen items; the tray count holds it, but the inbox does not lift it. Should open required and new messages float above seen ones? Default here: keep the order honest (by time) and let the tray carry the weight; Mark may prefer the lift.
  9. The lint advises, never blocks. The send form's voice lint flags an em dash, an exclamation point or an R4 word and leaves Send enabled; only the lengths and the required permission are hard checks (bones §9). Confirm, or make a flagged sentence a second click.
  10. Ended messages in the inbox take the acknowledgement's note with them. When a message ends from an action, the card moves under the fold at once and the one-line confirmation ("Acknowledged; HQ sees it") has nowhere to show. A short toast in the native app (the Root's notification host, c131 D8) is the likely answer; the mock shows none.

Answered by Mark on 2026-11-11

  1. The person decides; tray is the default for enforcement notices (R3 holds, no floor).
  2. Prompt for alert permission at Connect, once, with the reason. And the delivery levels become three plain modes per kind, quiet · normal · priority, each one OS interruption level (macOS passive · active · time-sensitive; Windows toast priority and Focus Assist): priority is the ceiling and only required messages may use it; "critical" and "danger" never appear (R4). The person may set any kind quieter.
  3. Yes for V1: without the time-sensitive entitlement, required is notify plus the tray plus the inbox flag.
  4. Acknowledge from the notification stays off; the notification opens the card.
  5. One sender label per plane plus the operator's name.
  6. Admin › Messages.
  7. One-way Contact IT (a mailto from qcontrol's it_contact) until the machine connects.
  8. Lift: open required and new messages float above seen ones; within each band, by time (changes bones §5's order).
  9. The lint's strictness is a plane setting, voice_lint: advise | confirm, default advise; confirm makes a flagged message ask "send anyway?" once.
  10. A short toast in the native app's notification host (c131 D8); the web mock gets the same.

The lab mock follows these in Phase 3 (the three modes in settings, the lifted order, the toast, the lint setting).

Verified on 2026-11-09

The three pages were checked on the live design server (:3000) with curl (200, server-rendered content) and in the browser pane: no console errors; the orange severity pill styled from the new files; acknowledging the censure moved it to acknowledged and dropped the tray count from three to two; a message sent from the operator's page appeared in the person's inbox as new over an in-app link (shared visit state); the lint flagged a deliberate em dash, exclamation point and "urgent". Captures: public/cycles/design/c137-messaging-bones.png (+full), c137-messaging-inbox.png, c137-mock-messages.png (+full), tagged with the registry's subjects.

Qpoint Brand Style Guide