Skip to content

refactor(ui): native notices and status labels on every surface - #4007

Merged
datlechin merged 15 commits into
mainfrom
refactor/native-notices
Nov 3, 2026
Merged

datlechin merged 15 commits into
mainfrom
refactor/native-notices

Conversation

@datlechin

@datlechin datlechin commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Two shared notice shapes in Views/Components, used wherever a notice was hand-drawn:
    • PaneNoticeBar: the bar over a pane, shaped like the transaction and recovery bars. Tinted symbol, a semibold title over secondary text, small stock buttons, icon-only borderless dismiss, .paneBarBackground().
    • NoticeLabel: a status line in a form, sheet or popover. The hue is on the symbol only; the words are secondary.
  • Pane banners (connection warning, file changed on disk, query error, AI chat error, parameter panel close) are PaneNoticeBars. No tinted fills.
  • Settings > Extensions:
    • plugins that could not load are rows under Couldn't Load in the plugin list; the detail pane shows the reason, the action (Update Now, Check for Updates…, Try Again) and Remove; no banner, no Dismiss, no 168 pt scroll area;
    • a pending relaunch is a row at the top of the list with Relaunch;
    • Open Plugin Settings from a failed connect selects the rejected row;
    • Try Again fetches the manifest, repairs what it can and rewrites each reason; "Check your connection and reopen TablePro" is gone.
  • Settings > General: the language note has a Relaunch button.
  • MCP: MCPServerState.failed carries a typed MCPServerFailure. The Status row reads Couldn't start or Stopped unexpectedly, the cause under it, Try Again beside it. A kernel-picked port says the configured one was in use. The message.contains("48") match is gone.
  • AI: AIProviderStatus is the one status per provider. The Settings row and the provider sheet render it. A Copilot error now shows on the row, and OpenCode or a custom provider without its optional key reads as ready. The chat panel's two no-provider texts are one.
  • The Notifications pane reads the permission again when TablePro becomes active.
  • Info-only alerts removed where the result shows in place:
    • Team Library publish (Welcome and Favorites): the section updates after the pull; the Favorites publish error is an error sheet;
    • linking a folder that is already linked selects its row;
    • sign-in from the connection form re-runs Test, so the Test button reports;
    • materialized view refresh: open tabs already reload; VoiceOver hears it;
    • maintenance: a clean run is announced; a server note (MySQL Msg_text, SQLite integrity_check) still shows, now read from the rows, where it always was;
    • Rewind history drawer: Restore Previous Values… is disabled for another connection's save and on a tab Rewind cannot act on, instead of answering with an alert.
  • Copy confirmations: no "Copied" swap or 1.5 s timer; VoiceOver hears "Copied to clipboard". MCP tokens copy through writeSecretText.
  • About 110 status texts and labels tinted whole now tint only their symbol. Yellow becomes orange.
  • Colored dots: connection color as a ConnectionTile, tag color as a tinted tag.fill. Dead ColorDotView and the section accentColor deleted.
  • Capsule chips (type badge, keywords, token permission, walkthrough importance, object kind, keycaps, AI context chips) are plain secondary text.
  • Tags in the connection form are an NSTokenField: type to complete or create a tag, Delete to remove one. The pull-down beside it keeps colors, Create New Tag… and Manage Tags.
  • Sheet notices on boxes are NoticeLabel rows. Em dash placeholders are "-" or "None". Floating material buttons over inspector wells moved to a header row. Dashboard and Query Insights cards are GroupBox.
  • iOS:
    • the "Row updated" capsule toast is gone: the saved values and the success haptic say it, and VoiceOver hears it;
    • NULL in a row's editor is a .button toggle; the insert form's Default, NULL or Value menu is a bordered button;
    • MetadataBadge (types, index kinds, row counts) is secondary text; "Primary" is no longer an orange chip;
    • the query result notices drop their material; the saved query keyword drops its capsule.
Defect on main Fixed by
"Port is already in use" matched any MCP failure text containing "48", such as a socket path under /var/folders/48/ Typed MCPServerFailure
A busy MCP port fell back to a kernel port with nothing on screen saying so "Port %d was in use." under the Status row
The only MCP retry was turning the server off and on Try Again
A Copilot service error showed only inside the provider sheet; the row said "Not signed in" AIProviderStatus.failed on the row
OpenCode with no key read "Not configured" though its key is optional Optional-key providers resolve from the endpoint
The row and the sheet wrote the Claude Code status twice, in different words One status
A rejected plugin's "Check your connection and reopen TablePro" asked for a relaunch with no button; "will try again the next time it launches" likewise Try Again, retryRejectedPluginRepairs
The language note asked for a restart with no button Relaunch
The Notifications pane kept a permission turned off in System Settings until it was rebuilt NotificationPermissionMonitor
MySQL Msg_text and SQLite integrity_check rows (OPTIMIZE notes, CHECK errors) were never shown; the alert read the empty statusMessage MaintenanceResultNote
The Rewind drawer offered Restore Previous Values for another connection's save, then answered with an alert Item disabled
The Favorites publish error showed in an info sheet Error sheet
MCP tokens copied through NSPasteboard directly, so clipboard history apps kept them writeSecretText
MCPPortAllocatorError text was shown for socket failures too Detail from the failure case

Decisions taken without a design review:

  • Tags: an NSTokenField keeps native typing, completion and Delete, but it cannot color a token through public API, so the colors stay in the pull-down beside it. A typed name that matches no tag creates a gray one.
  • Pane notices use the transaction bar's material; in sheets the same shape draws without it.
  • Warnings tint orange, errors red, success green, all on the symbol. Yellow text failed contrast in Light.
  • The AI chat panel shows one no-provider state. It does not show the active provider's sign-in or key state: that needs a Keychain read or a CLI or language-server probe on every open, which no existing trigger runs.
  • Maintenance keeps a sheet only when the server returned a note.
  • The JSON and text inspector fields put Open in Window in a header row above the well.
  • The Query Insights Activity section moved into the view beside the other sections, so the chart renders on its own.

Deliberately not fixed here:

  • Restore Previous Values… still answers "Nothing to Restore" when no snapshot is kept, from the Edit menu and the drawer: knowing it needs an encrypted read on the history actor, and a set read when the drawer loads went stale while a save's snapshot was still being written (Codex round 4), so that part was cut.
  • The Entra device-code sheet stays: it carries the code to type.
  • Transfer and import result alerts belong to the running-work refactor; the Welcome import alert reports failures and stays.

Tests changed on purpose, because they pinned the old shape:

  • FieldDrivenListEntryTests.headerIdentityFollowsItsAccent and ConnectionSwitcherFilterTests.groupHeadersHaveNoDot: the section accent color is gone;
  • MCPServerManagerIdleStateTests.testServerStatesAreDistinguishable: typed failure;
  • FavoritesLinkedFolderTests.reportsAnAlreadyLinkedFolder: carries the folder id;
  • ConnectionSignInRegistryTests.providersAreFullyDescribed: signedInMessage is gone.

Test plan

  • New suites:
    • PluginListSelectionTests (5), MCPServerStatusPresentationTests (5), AIProviderStatusTests (7), NotificationPermissionMonitorTests (2), ConnectionTagTokensTests (3), ConnectionTagTokenFieldTests (7, a real NSTokenField in a window: typing alone commits nothing, Return and ending editing commit, a repeat or a refused name never stays), MaintenanceResultNoteTests (6);
    • new cases in PluginRegistryUpdatePassTests (Try Again repairs, reports a refused registry, needs updating on) and MCPServerOffContractTests (Try Again refused while MCP is off).
  • The suites of every changed type, 34 suites: 292 executed, 290 passed; the 2 Query Insights pixel tests failed because ImageRenderer draws no GroupBox, fixed by moving the Activity section into the view; rerun 15 of 15.
  • iOS: RowDetailViewModelTests 52 of 52 on iPhone 17 Pro, iOS 26.
  • macOS Unit plan on the tree merged with main: 28,524 executed, 28,524 passed, 0 failed, 2 skipped.
  • SwiftLint on every changed file: 0 violations. scripts/check-banned-words.sh --base origin/main: clean. localization.py plugins: ok; 20 new keys added.
  • Codex reviewed four rounds:
    • round 1: the token field committed typed prefixes as synced tags. Fixed: only a tokenized name commits;
    • round 2: Save could drop a tag still being typed, and the tests released the weak delegate. Fixed;
    • round 3: Copilot read as Checking forever after its idle stop; a tag created elsewhere was dropped as a duplicate. Fixed;
    • round 4: the drawer snapshot set went stale while a snapshot was being written. Cut back to the checks known when the list loads.
  • Not verified:
    • Try Again on a plugin and on MCP against a real failure: Debug builds turn plugin updating off, and the MCP failure in the shots is seeded;
    • macOS 13 to 15 layout: no such OS on this machine;
    • the iOS NULL toggle in edit mode: the shot harness cannot tap Edit, so the shots show the insert form and the read view.

Screenshots

States that need a failure are seeded by a temporary patch that is not in this PR: a server warning on connect, two plugins that could not load and a pending relaunch, an MCP bind failure, a changed language, three AI providers. The query error is a real failed query. Before is main at de5c918.

Before, light Before, dark After, light After, dark
Pane bars: connection warning and query error Before Pane bars: connection warning and query error Before Pane bars: connection warning and query error, dark After Pane bars: connection warning and query error After Pane bars: connection warning and query error, dark
AI chat panel with no provider Before AI chat panel with no provider Before AI chat panel with no provider, dark After AI chat panel with no provider After AI chat panel with no provider, dark
Settings > Extensions: plugins that could not load, relaunch Before Settings > Extensions: plugins that could not load, relaunch Before Settings > Extensions: plugins that could not load, relaunch, dark After Settings > Extensions: plugins that could not load, relaunch After Settings > Extensions: plugins that could not load, relaunch, dark
Settings > MCP: failed to start Before Settings > MCP: failed to start Before Settings > MCP: failed to start, dark After Settings > MCP: failed to start After Settings > MCP: failed to start, dark
Settings > General: language changed Before Settings > General: language changed Before Settings > General: language changed, dark After Settings > General: language changed After Settings > General: language changed, dark
Settings > AI: provider status Before Settings > AI: provider status Before Settings > AI: provider status, dark After Settings > AI: provider status After Settings > AI: provider status, dark
Import from a link: identity tile, startup SQL notice Before Import from a link: identity tile, startup SQL notice Before Import from a link: identity tile, startup SQL notice, dark After Import from a link: identity tile, startup SQL notice After Import from a link: identity tile, startup SQL notice, dark
Connection form tags Before Connection form tags Before Connection form tags, dark After Connection form tags After Connection form tags, dark

iOS 26 (iPhone 17 Pro simulator; the real views in a window)

Before, light Before, dark After, light After, dark
Insert row: Default, NULL or Value control, type labels Before Insert row: Default, NULL or Value control, type labels Before Insert row: Default, NULL or Value control, type labels, dark After Insert row: Default, NULL or Value control, type labels After Insert row: Default, NULL or Value control, type labels, dark
Row: type labels Before Row: type labels Before Row: type labels, dark After Row: type labels After Row: type labels, dark
Docs: TableProApp/docs#167

@datlechin
datlechin enabled auto-merge (squash) November 3, 2026 07:02
@datlechin
datlechin merged commit 3910543 into main Nov 3, 2026
11 checks passed
@datlechin
datlechin deleted the refactor/native-notices branch November 3, 2026 12:57
datlechin added a commit that referenced this pull request Nov 3, 2026
Brings main's 27 commits, among them one license and sync presentation
(#4005), the Black Friday line (#3995), the update indicator and relaunch
(#3993, #3997), running work (#4009), native notices (#4007) and the
security merges for GHSA-3x6q-h487-r38w, GHSA-3xf7-86qj-4546,
GHSA-6g2g-65r6-v5wh, GHSA-q2hp-vxv3-87fw, GHSA-6p9q-hc7v-cxmx and
GHSA-vg8g-h6xp-6px5.

- #4005: ported onto FeatureAccess. LicensePresentation.gate reads
  AccountState: signed out names a paid plan and offers Sign In beside
  Purchase License, an account without the feature gets Upgrade Plan, the
  device limit opens Settings > Account, and a lapsed or failed check reads
  as Settings > Account words it, with Try Again, so a paying customer is
  never sent to the pricing page. The pane's notice text moved to
  AccountSettingsContent.Notice so both read it. LicenseGateRow replaces
  ProBadge in the Sync, Linked Folders, Data Rewind and connection export
  rows, LicenseGateAlert serves Compare & Sync and Data Rewind, and the
  overlay renders the same notice. Try Again shows a spinner and the
  failed check's reason; Refresh in Settings > Account shows the reason
  too. Welcome shows the pane's first notice as a link (a plan being
  canceled stays in the pane), else Sign In. Dropped: the .suspended
  access, since an entitlement carries no suspended state, and
  LicenseManager's coalesced checks and stale-answer guard, since
  AccountServices already runs one check at a time and drops a refresh
  that lands after sign-out.
- #3995: the Black Friday line shows when FeatureAccess.supportAudience is
  .prospect, the notion the day-30 ask reads.
- #3989: ported before this merge, in e39c309.
- #3993, #3997: merged as main has them; Welcome and the Sync pane keep
  UpdateCenter.
- #4007: a Team Library publish that goes through shows no info alert on
  Welcome or in Favorites; the section shows the items. The queued and
  failed messages stay.
- #4009: merged outside the Team Library. TeamLibraryStore's write
  generation, the shared in-flight pull, the refused second publish and
  the Team Library row in Settings > License are dropped: TeamLibrarySync
  runs syncs and publishes one at a time, bumps a generation on sign-out,
  and the Welcome and Favorites headers show the running sync, the last
  sync and a failure.
- 726a167 (security): LicenseAPIClient and LiveTeamLibraryAPIClient
  follow redirects only on their own origin. Ported by giving the account
  URLSession SameOriginRedirects as its delegate, which covers token
  requests, /v2 calls and the team library. The pulled library keeps a
  connection's sign-in choice for the consent prompt: ported to
  TeamLibraryChange.connection, and its test moved to TeamLibrarySyncTests.
  Publishing needs nothing, since portableSettings keeps the choice by
  default. EnvVarReferenceConsent reads FeatureAccess.
- DatabaseManager+Sessions: main's recovery guard and consent path, with
  Telemetry.connectionAttempted in place of MacAnalyticsProvider.
- project.yml: PostHog stays and zstd goes, as main moved Kafka's codecs
  into TableProCore; the app links main's new products, without
  TableProAnalytics.
- The storage rule, data-sync-storage.md: the paths main listed for the
  deleted TeamLibraryModels, TeamLibraryStore and MacAnalyticsProvider name
  TeamLibraryItem, TeamLibraryState and Telemetry, plus Account's stores.
- Localizable.xcstrings: merged key by key in this branch's order; no key
  changed on both sides. 271 keys come from main; the three SSH host keys
  main removed are gone, as nothing uses them.
- Tests deleted with the code this branch deleted: LicenseManagerDecisionTests,
  LicensePresentationTests and LicenseTierTests under Models,
  TeamLibraryModelsTests, TeamLibrarySyncCoordinatorTests, and the Macs
  link case in SupportLinksTests. New: LicensePresentationTests under
  Views/Settings, the sign-in choice case in TeamLibrarySyncTests and the
  redirect case in AccountConfigurationTests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant