Skip to content

Latest commit

 

History

History
139 lines (130 loc) · 28.9 KB

File metadata and controls

139 lines (130 loc) · 28.9 KB
title CodexPlusBar Learnings
status active
last_updated 2026-03-18
purpose Keep non-obvious project knowledge in one place so future agents can avoid repeated tool mistakes, API misunderstandings, and fragile fixes.
update_policy
Read this file before debugging, tool-heavy work, networking changes, or test repair.
Append new knowledge after any problem or bug that takes extra effort to solve.
Prefer short dated notes under the closest section instead of rewriting history.

Learnings

Table of Contents

How To Use This File

  • Read this file before you change networking, session handling, account loading, dashboard UI, or tests.
  • Treat this file like a workshop notebook: it is for practical lessons, not polished documentation.
  • If a note becomes outdated, add a new note that corrects it. Do not silently delete the old lesson unless it is clearly wrong and harmful.

How To Update This File

  • Add new notes only when they would save the next agent time or prevent a bug, bad tool call, or wrong assumption.
  • Append under the closest section with a date prefix like 2026-03-18.
  • Keep each note short and concrete:
    • problem
    • fix
    • prevention
  • Good example: 2026-03-18: /wham/usage is account-scoped by ChatGPT-Account-ID, so do not add workspace-switch fallback logic for multi-account limit fetches.
  • If you learn something from a failed build, test, or tool call, record the exact trap and the safer pattern.

Build And Test Commands

  • 2026-03-18: Use xcodebuild test -project CodexPlusBar.xcodeproj -scheme CodexPlusBar -destination 'platform=macOS' as the main verification command for this repo.
  • 2026-03-18: For normal local verification in this repo, prefer the Makefile entry points the project already wraps: make test, make agent-verify, and TRACE_PRIVATE_API=1 make build-and-run-background.

Tooling Notes

  • 2026-03-18: Proxyman is useful for confirming private ChatGPT web calls in this app. It is especially helpful for checking real request headers like ChatGPT-Account-ID and matching them to returned account data.
  • 2026-03-18: When you need quick repo-wide search here, rg is enough for most code and test discovery. There is no existing root onboarding file beyond AGENTS.md, so future onboarding rules should live there.
  • 2026-03-19: Pencil MCP flagged SF Pro Display, SF Pro Text, and SF Mono as invalid font families during the menubar/app redesign, but the canvas still rendered readable fallback text. For Pencil work here, always verify with screenshots instead of trusting font validation warnings alone.
  • 2026-03-19: For multiple bundled font families in this macOS app, point ATSApplicationFontsPath at the top-level Fonts folder and also register .ttf/.otf files at launch. If Info.plist only points at one subfolder, Manrope and Cormorant can fall back silently and the redesign loses its intended typography.
  • 2026-04-20: The persistent WebKit profile API names differ in Swift from many docs/examples. Use WKWebsiteDataStore(forIdentifier:) to open a profile store and WKWebsiteDataStore.remove(forIdentifier:completionHandler:) to delete one. Do not search for dataStoreForIdentifier in Swift code and assume it will compile.
  • 2026-04-21: Sketch MCP document.save(...) can write the .sketch file to the requested path even when the returned doc.path still shows the old unsaved/iCloud location. After saving through Sketch MCP, verify the target file on disk instead of trusting the reported path string.
  • 2026-04-24: The local screenshot skill helper can fail on this Mac with a Swift JIT NSScreen symbol error even after Screen Recording permission is granted. For visual verification, fall back to macOS screencapture -x <path> and inspect the PNG with view_image.
  • 2026-05-28: Chrome launched with a custom temporary --user-data-dir and --remote-debugging-port=0 exposes a browser DevTools websocket from /json/version that accepts both Browser.getVersion and Storage.getCookies. Use a dedicated or temporary profile for probes; never read the user's default Chrome cookie database.
  • 2026-05-28: Do not keep --remote-debugging-port=0, DevTools, or headless flags on the visible ChatGPT sign-in browser. Cloudflare can treat those as automation signals and loop the human check. Launch normal visible Chrome first, then use a short hidden CDP launch only after the user clicks Sync.
  • 2026-05-28: Touch ID in Chrome unlocks a passkey from the current passkey store; it is not the passkey store itself. A dedicated --user-data-dir cannot see passkeys saved only in the user's normal Chrome profile, so OpenAI may fall back to "security key" UI even on a Mac with Touch ID. Use Apple Passwords/iCloud Keychain, Google Password Manager sync in the helper profile, or a different-device passkey flow.
  • 2026-05-28: Do not switch CodexPlusBar's cookie source to the user's normal Chrome profile, even on a personal Mac. Chrome 136+ blocks remote debugging for the default data directory, so a normal-profile sync would require cookie database scraping, a browser extension, or UI scripting. Keep the dedicated profile for CDP import; use normal Chrome only as a manual setup aid.
  • 2026-07-08: Google Chrome Sync sign-in accepts service=chromiumsync with login_hint, but returns HTTP 400 if continue points at ChatGPT. Open the Google account hint URL and the ChatGPT login URL as separate Chrome tabs instead of nesting ChatGPT inside the Google sign-in URL.
  • 2026-05-28: gh release create --target can reject a short commit hash with Release.target_commitish is invalid. Use the full 40-character SHA for snapshot releases, then fetch the created tag locally with git fetch origin tag <tag>.
  • 2026-05-29: The default archive/DMG path can produce an ad-hoc signed app and an unsigned DMG because the project has no Release Developer ID settings yet. For notarized snapshots, copy the archived app to a packaging workspace, sign it with Developer ID Application plus --options runtime --timestamp, create the DMG from that signed copy, sign the DMG, then notarize and staple before checksum/upload.
  • 2026-07-07: For release checksums, generate the .sha256 file from inside build/dist with the DMG basename, then verify from that same folder. If the checksum file stores build/dist/..., shasum -c fails after cd build/dist even though the DMG itself is valid.
  • 2026-07-09: Do not run xcrun stapler staple and xcrun stapler validate in parallel. validate can race before the ticket is attached and report a false failure; run staple, then validate, then spctl.
  • 2026-07-17: notarytool Keychain profile names are case-sensitive. This Mac's working release profile is CodexPlusBar-notary; CodexPlusBar-Notary does not exist, so check the exact saved name before treating notarization credentials as missing.

Backend API Notes

  • 2026-03-18: /backend-api/accounts/check/v4-2023-04-27 is the source of truth for multi-account catalog data. It provides account names, plan types, entitlement dates, accessibility flags, deactivation state, and account_ordering.
  • 2026-03-18: /backend-api/wham/usage is account-scoped by the ChatGPT-Account-ID header. The URL does not change per account; the header decides which account limit payload you get back.
  • 2026-03-18: Do not overwrite an explicit ChatGPT-Account-ID in the request signer. If the caller already set it, preserve it.
  • 2026-03-18: In the native menu bar app, header-only wham/usage requests can still return the currently pinned account for every response. The working fix was to switch the app session to each target account first, then call wham/usage, then restore the original account at the end of the sweep.
  • 2026-03-18: The real browser workspace switch call is a cookie-only GET /api/auth/session?exchange_workspace_token=true&workspace_id=...&reason=setCurrentAccount. Do not add Authorization, ChatGPT-Account-ID, or OAI-Language to that request. Decode the switch response directly and verify the returned account before calling /wham/usage.
  • 2026-03-18: ChatGPT workspace switching in the native app also depends on the _account cookie, not just workspace_id in /api/auth/session. Set _account to the target account before each switch and before restore, or the backend can keep the session pinned to the old account.
  • 2026-03-18: The timezone_offset_min query for accounts/check must follow the browser Date.getTimezoneOffset() sign convention. Example: Asia/Jakarta should send -420, not 420.
  • 2026-03-18: accounts/check can return duplicate logical account_id values under different entries. Normalize and merge account catalogs before building dictionaries or starting per-account limit fetches, or the app can crash on Dictionary(uniqueKeysWithValues:).
  • 2026-04-07: When /backend-api/wham/usage is slow, plain timeout text is not enough to debug. Emit trace-gated structured logs (event=limit_fetch_timeout and event=limit_fetch_slow_success) with account, sweep index, checkpoint (switch/request/decode/restore), elapsed/timeout thresholds, and session+_account cookie before/after. Prevent future guesswork by grepping event=limit_fetch_ before changing timeout values.
  • 2026-04-07: Timeout traces can look "missing" when they depend on stdout capture and trace env flags. Keep slow-success logs trace-gated, but always persist timeout diagnostics to a durable local file (~/Library/Application Support/CodexPlusBar/Logs/limit-fetch-timeouts.log by default, overridable with CODEXPLUSBAR_TIMEOUT_DIAGNOSTICS_LOG) and pass the same typed diagnostics into the account card message so users can see exact timeout context without opening logs.
  • 2026-04-07: If multi-account refresh fails only at checkpoint=switch around the same cutoff (for example ~20s), the trap is a single shared timeout covering both switch and usage phases. Fix by splitting timeout budgets per stage (switch, request, restore) and set a longer switch request timeout on /api/auth/session?exchange_workspace_token=true so URLSession does not cancel early. Prevent regressions by asserting stage-specific timeout_ms values and explicit switch request timeout in tests.
  • 2026-04-20: Even in the Plus-only flow, AuthSessionService.fetchCurrentSession can still hit /codex/settings/usage after /api/auth/session when the cached auth context does not yet have clientVersion, account, or token details. In tests, either seed a fallback auth context or mock the bootstrap HTML too, or the request sequence looks wrong even though the runtime path is correct.
  • 2026-05-28: ChatGPT can store the auth session as chunked cookies like __Secure-next-auth.session-token.0 and .1. Treat those numeric chunks as a valid session-cookie family and import all ChatGPT cookies; otherwise Chrome can be visibly logged in while the native app still reports unauthorized.
  • 2026-07-15: A ChatGPT session cookie can change between chunked (.0, .1) and unchunked forms after a new login. Replace the profile's existing chatgpt.com cookies during Chrome import instead of adding to them, and never fall back to the shared HTTPCookieStorage; mixed old/new cookie families make /api/auth/session return logged out even while Chrome is visibly signed in.
  • 2026-04-21: Menu bar entitlement expiry should come from /backend-api/accounts/check/v4-2023-04-27, not from the auth session expiry or the wham/usage payload. Fetch the catalog after a successful usage refresh, match by account ID, and preserve the last known expiry if the catalog request fails so a transient metadata miss does not erase a useful expiry line.
  • 2026-04-24: accounts/check can return the current personal account twice: once under the UUID key and once under accounts.default. The wham/usage account ID can match account_owner_id or account_user_id, not only account.account_id, so entitlement expiry matching must keep those aliases and fall back to the default entry before showing Expiry unavailable.

Swift And Testing Pitfalls

  • 2026-03-18: In Swift Testing, avoid nesting #require(...) inside another #require(...) expression. Split the values into separate local constants first, or the macro can recurse badly and fail compilation.
  • 2026-03-18: Swift 6 can struggle with type inference around Dictionary(uniqueKeysWithValues:) when the input comes from compactMap. If inference fails, give the dictionary an explicit type like [String: WorkspaceLimitSnapshot](...).
  • 2026-03-18: For this repo, bounded async loading was safer to implement with chunked batches of Task { @MainActor ... } than with the earlier task-group attempt that ran into Swift 6 isolation/compiler trouble.
  • 2026-03-18: When testing dates from ISO strings, compare against a parsed ISO8601 date instead of hand-written epoch seconds. Manual epoch values are easy to get wrong and create fake decoder failures.
  • 2026-03-18: ChatGPTRequestSigner now gives /backend-api/wham/ calls browser-style headers like Accept: */*, Referer: /codex, and X-OpenAI-Target-*. If signer tests still expect the older JSON usage-page headers, the tests fail even though the runtime fix is correct.
  • 2026-03-19: This repo treats warnings as errors, so old single-parameter SwiftUI onChange closures now fail macOS builds. Use the two-parameter macOS 14+ form during UI refactors, or the test/build step will stop on a deprecation instead of a runtime bug.
  • 2026-04-20: In AppKit-hosted SwiftUI tests here, a styled SwiftUI Button may not expose its visible label through NSButton.title. For disclosure regressions, verify the mounted content state directly (for example WKWebView present or absent) and use a small injected initial state when needed instead of scraping button titles.
  • 2026-04-20: In AppKit-hosted SwiftUI tests here, static Text inside styled cards or sidebar rows may not appear as NSTextField descendants at all. For UI copy/layout contracts, prefer testing a shared presentation model or explicit accessibility value instead of scraping sidebar labels from the NSView tree.
  • 2026-04-20: For narrow MenuBarRootView regressions where AppKit view scraping still hides styled row content, a lightweight structural check on type(of: rootView.body) can confirm an old inline-card view is gone. Use that only for small containment regressions; keep real behavior assertions in shared presentation tests first.
  • 2026-04-21: The same view-scraping trap also applies to inline Save buttons inside ProfileManagerWindowView. Even when the buttons are visible in SwiftUI, the hosted NSView tree here may expose neither NSButton.title nor a matching NSTextField. Prevent flaky tests by asserting editable-field structure or shared presentation/state, not visible button copy.
  • 2026-04-28: xcodebuild -only-testing:CodexPlusBarTests/... can compile this Swift Testing target but run 0 tests, which is a false green for focused checks. Use make test for final verification, and confirm the Swift Testing summary shows the expected test count.
  • 2026-07-08: A failed make test can report the Swift Testing failure and then leave make/xcodebuild hanging. Confirm the failure first, then kill only the stuck make/xcodebuild PIDs before continuing; do not leave the session open.
  • 2026-07-15: Swift 6 could not type-check one chained enumerated + compactMap + sorted + map email-ranking expression. Build the typed ranked array in a small loop, then sort and map in separate steps; this compiles faster and keeps ranking logic readable.

UI And Data Flow Notes

  • 2026-03-18: The app now uses a dashboard-first account flow, not a selection-first workspace flow. Keep names and expiry dates visible for all returned accounts, and keep raw account IDs internal where possible.
  • 2026-03-18: Partial limit failures should stay local to the affected account card. If accounts/check succeeds, the dashboard should still render even when one or more wham/usage calls fail.
  • 2026-03-18: A full multi-account limit sweep can take longer than the old 12-second controller stage timeout. Do not wrap the whole dashboard load in one timeout or wait for every account before updating the UI. Load the current account first, update cards progressively, and map cancellations to friendly timeout/interrupted messages instead of showing raw cancelled.
  • 2026-04-06: The account catalog refresh can also exceed the old shared 12-second stage timeout even when the request is still in flight. If users see Loading the account list took too long, inspect the unified log for a -999 cancellation around 12 seconds before blaming ChatGPT; the safer fix is separate stage budgets so accounts/check is not cut off as aggressively as session validation.
  • 2026-03-18: The compact menu bar surface should summarize urgency and counts, while the account window holds the full scrollable card grid and the embedded session repair web view.
  • 2026-03-18: In the account window, a vertical ScrollView inside VSplitView can collapse to intrinsic content width unless the inner dashboard stack is explicitly framed to the available width. When that happens, LazyVGrid cards and the session web view squeeze into unreadable narrow columns.
  • 2026-03-19: In the menu bar panel, CodexCard inside a ScrollView can also hug its intrinsic content width. When a compact card should fill the panel, explicitly frame the card or its root content to maxWidth: .infinity, or the panel shows large empty gutters even though the outer window is wide enough.
  • 2026-03-19: A MenuBarExtra(.window) panel can still shave the right edge even when the NSWindow is already at the intended size. The practical fix here was to keep the outer window at 484 x 560, then reserve an explicit outer chrome inset for CodexShell and give the shell content an explicit measured frame inside that surface instead of trusting implicit ZStack padding proposals.
  • 2026-03-19: If the menu bar panel still truncates on the right after the outer window is correct, inspect the live accessibility bounds before widening anything. In this app the real trap was a ScrollView content area growing wider than the 484pt panel plus fixedSize(horizontal: true) badge rows, so the fix was to lock the menu content to the computed inner width and let the left text truncate before the badges.
  • 2026-04-20: On this Mac, the MenuBarExtra(.window) status item could render and receive clicks without ever realizing a popup window, even with a minimal static popup body. The reliable fix was to keep the SwiftUI panel view but host it through an AppKit NSStatusItem plus NSPopover bridge instead of relying on the SwiftUI popup scene.
  • 2026-03-18: Hide expired and deactivated accounts before creating dashboard items and before starting per-account limit fetches. If you only hide them in SwiftUI later, the app still wastes wham/usage calls and can leave hidden rows stuck in a loading state.
  • 2026-03-18: For countdown text like Reset in ..., use resetAt as the source of truth. reset_after_seconds is only correct at fetch time and goes stale if the UI stays open.
  • 2026-03-18: TimelineView inside a MenuBarExtra label caused an invisible status item and multi-GB layout churn on macOS 26. Keep menu bar labels plain and drive time updates through explicit observed state instead.
  • 2026-03-19: The embedded ChatGPT sign-in web view can navigate through /auth/ and usage pages during normal browsing. Do not treat those navigations as account-data refresh signals, or the app will ask ChatGPT for limits far more often than the 5-minute timer.
  • 2026-03-19: For ADHD-focused dashboard work, do not create cards, repeated facts, or header copy that add no decision value. Keep shared facts like refresh time once at the top, keep expiry visible where it helps account choice, and remove decorative summary blocks that only increase cognitive load.
  • 2026-04-07: The app should now show only 5H and 7D windows. Keep decoding tolerant if ChatGPT still sends code_review_rate_limit, but do not surface that window in models or UI to avoid stale, misleading dashboard sections.
  • 2026-04-07: A background refresh can fail during checkingSession after the app already has valid cards on screen. Do not force authState back to .signingIn for every refresh; restore the previous presentation state when that first stage times out, and keep a longer default budget than 12 seconds because real /api/auth/session calls can exceed it.
  • 2026-03-23: For menu bar priority selection, keep the chosen account ID in @AppStorage at the view layer and let both the panel content and the status label read the same key. If only the panel owns that state, the menu bar text and the opened panel drift out of sync.
  • 2026-04-20: In this AppKit-bridged menu bar app, @AppStorage in the SwiftUI panel does not automatically refresh the custom NSStatusItem label. When the top label depends on a stored preference, observe UserDefaults.didChangeNotification in the status-item controller and recompute the title there, or the panel selection changes but the menu bar text stays stale.
  • 2026-03-24: For a transparent custom account window title bar on macOS, do not stop at titlebarAppearsTransparent = true. Add .fullSizeContentView to the NSWindow style mask too, or macOS can leave a visible top chrome seam that looks like an app border bug.
  • 2026-03-24: If you want real desktop-through glass in that transparent title-bar strip, lowering the shell alpha is not enough. Use an AppKit NSVisualEffectView with .titlebar material and .behindWindow blending for the top strip, then keep the main shell body separate and opaque enough for readable content.
  • 2026-03-24: If the goal is a truly clear native title bar, do not paint your app shell or custom glass into the title-bar height after enabling .fullSizeContentView. Leave that strip empty and start the styled body below contentLayoutRect, or the title bar turns into fake content again.
  • 2026-03-24: If a 1px dirty line still remains after moving the body below a clear title bar, the problem can be the body shell itself: clipped backdrop and shadow pixels touching the transparent strip. Bury the body upward by one device pixel and stop shadow bleed at the top edge.
  • 2026-03-24: The new SwiftUI Liquid Glass APIs (glassEffect, Glass.clear, Glass.tint) are macOS 26+ only. In this macOS 14-targeted app, gate them with availability and keep an AppKit title-bar visual-effect fallback for older systems.
  • 2026-03-24: If a transparent title-bar glass cap still looks rectangular, the problem can be the glass layer shape, not the window. Clip the title-bar material itself to a top-rounded shape that matches the shell radius, and keep the tint very light or the glass starts reading like a dark slab.
  • 2026-04-20: A custom NSStatusItem label can disappear in the live menu bar even when the button still has a real title if you force a custom title foreground color through attributedTitle and contentTintColor. Let the menu bar text use the system contrast color instead, and keep any styling limited to font or symbol choice.
  • 2026-04-20: In the menu bar panel, do not wrap a whole profile row in one outer Button if the row also needs visible inline actions like copy, email, or pin. Keep one primary button region inside the row and render secondary controls as sibling buttons, or SwiftUI composition and interaction become fragile.
  • 2026-05-20: For visual checks of the LSUIElement menu bar app, clicking the panel footer icon can close the popover before the manager window appears. If that happens, post the distributed reopen notification com.linda.CodexPlusBar.reopenAccountWindow to open CodexPlusBar Profiles reliably.
  • 2026-07-07: For theme visual checks, do not trust a nearby menu bar label without confirming the owner app. Other status items can look similar. Use the distributed reopen notification com.linda.CodexPlusBar.reopenAccountWindow, then verify the window owner is CodexPlusBar before taking screenshots.
  • 2026-07-07: When adding Everforest Medium and Soft presets, do not use quiet gray as useful support text on raised surfaces. WCAG checks showed it can fail on bg2 and bg3 layers, so keep useful secondary text on fg/strongText and reserve gray for truly quiet metadata.
  • 2026-07-07: In an LSUIElement menu bar app, adding a SwiftUI Settings scene is not discoverable enough by itself. Add a visible compact-panel settings doorway, such as a labeled gear button, and keep the normal Settings scene as the destination.
  • 2026-06-06: When the UI needs urgency sorting by expiry, do not reorder PlusProfileController.profiles directly. That array also drives selection, move-up/move-down, and persisted sortOrder, so changing it turns a temporary display preference into saved data churn. Keep a separate display-only sort helper and apply it in the sidebar or menu bar view layer.
  • 2026-07-07: Do not force theme changes through .id(...) on CodexShell or whole-window roots. It makes SwiftUI throw away view identity, which can feel like a slow refresh and can reset scroll, selection, or draft state. Use small @AppStorage/environment theme context updates so only color tokens redraw.
  • 2026-07-07: Keep Everforest data accents separate from status accents. Use green for healthy usage data, aqua for success, blue for info, yellow/orange/red for warning-to-danger data, and strong text for light-theme dense values. This avoids color meaning drift while keeping WCAG and delta L* checks useful.
  • 2026-07-07: In Everforest Light, do not use the strongest dark neutral as the global primaryText. It makes labels, values, and button text collapse into one black/gray mass. Keep strong text for headings and dense values, use a WCAG-safe fg family for labels, and darken accent hues only for readable action/status/data text.
  • 2026-07-07: Card background changes must cover both shared CodexCard and custom row-card fills such as ProfileSummaryRow and EmailToolsSidebarRow. Tests can protect the token path, but still open CodexPlusBar Profiles and screenshot it because custom row cards can bypass CodexCard.
  • 2026-07-07: Raw Everforest accent colors can fail WCAG normal-text contrast after card fills move to bg0, especially red on Dark Soft. Route small accent text through readableAccentToken(...), then keep tests on the real card fill and main surfaces.
  • 2026-07-07: Tiny menu-bar text can still look washed out even when primary text passes WCAG if useful roles such as quietText are skipped and disabled controls use whole-view opacity. Add quiet/disabled text to WCAG checks, add a delta L* text guard, and change disabled controls to readable text/fill tokens instead of dimming the entire control.
  • 2026-07-08: Profile tags need a tag-specific tone layer, not only generic CodexStatusTone. Active and Pending can be technically different but still too close in Everforest dark compact chips, so pair status color with SF Symbols and test tag text against its own selected fill with WCAG and delta L*.
  • 2026-07-08: Profile tag product semantics override the default Everforest pending/attention taxonomy here: Need action is red, Active is green, and Pending is yellow. Keep that mapping in CodexProfileTagTone and protect it with tests so future theme cleanup does not make Need action and Pending look the same again.
  • 2026-07-08: Do not reuse profile tag text color as the tag border color. It makes Active, Need action, and Pending chips feel boxed-in and too loud. Keep tag meaning in the icon, label, and tinted fill, then keep the border as a low-contrast quiet edge with tests proving text WCAG/delta L* still passes.
  • 2026-07-08: Empty Profile Manager detail fields should look empty. Placeholder copy like sample phone numbers, password hints, or note examples can read as saved data and makes users re-check the form. Keep the field labels visible, but leave the field body blank until real saved text exists.
  • 2026-07-09: The Profile Manager OTP panel should default to a hidden safe state. Do not mount the one-second TimelineView or generate/validate TOTP until the user clicks Show OTP; reset reveal on profile change, window close, or 2FA key edits, and protect hidden/revealed presentation states with tests.
  • 2026-07-09: Hidden OTP should look redacted, not say Hidden. Use ProfileManagerOneTimePasswordPresentation.isCodeMasked plus CodexTheme.oneTimePasswordMaskFillToken so the code slot renders a quiet mask, avoids extra reading work, and stays inside the WCAG/delta L* theme-test path.
  • 2026-07-16: For reusable phone choices on macOS, an editable NSComboBox fits the existing SwiftUI form. Filter its item list from controlTextDidChange, compare digit-only values so punctuation does not block matches, and reset the filter after selection or profile sync so reopening the dropdown shows every saved number.