PR #731 — Synlynk UX 1.0: TUI + Vizor on Shared uxcore
99: Synlynk UX 1.0 — TUI + Vizor on Shared uxcore
Where we left off
In post #98 (PR #715), we analyzed the local-agent A/B test results and confirmed hardware limits were the gating factor. But in terms of user experience architecture, the system had reached a structural bottleneck. Prior to PR #731, Vizor was our sole visual HUD into repo state. Its internal data generator generate_viz_data() served as the implicit read path for web visualizations, but synlynk lacked a unified, testable UX core.
Specifically, there was no single write chokepoint, no Actor or role-based access control (RBAC) model, no feature-flag scoping, and no "Bring Your Own UX" (BYOUX) public interface. Terminal users had no live TUI, web users relied on ad-hoc HTTP handler routes directly calling dispatch_agent, and external extensions (such as chat notifications or external process monitors) had no event-stream interface to subscribe to. The goalpost was to unify both terminal and web interfaces on top of a shared, testable library kernel (synlynk/uxcore.py).
What moved the goalpost
Implementation followed a straight execution of the approved design spec (docs/superpowers/specs/2026-08-05-synlynk-ux-1.0-design.md) and plan (docs/superpowers/plans/2026-08-05-synlynk-ux-1.0.md) without mid-stream strategic pivots. However, one key architectural decision shaped how the unified core functions:
Rather than having Vizor's HTTP server or the new curses TUI call dispatch_agent directly, all mutation operations (dispatching agents, approving PRs, killing jobs) were routed through a single internal chokepoint: uxcore._execute_write().
This write chokepoint re-checks actor capabilities via list_capabilities(actor) before executing the underlying operation, and then appends a structured audit event to .synlynk/events.jsonl. Decoupling write execution from HTTP handlers and CLI dispatch loops is what made the reference Slack notifier and any future BYOUX consumer possible — third-party tools can consume structured events via uxcore.subscribe() without touching web handlers or parsing low-level subprocess stdout.
What this PR shipped
PR #731 shipped the core foundation of Synlynk UX 1.0 across 18 modified files (+3251 lines, -201 lines), delivered via 12 dispatched implementation tasks across our multi-agent workgroup (Codex for CLI/plumbing, Grok for algorithmic surfaces, Agy for documentation/templates) plus a final regression-fix pass:
synlynk/uxcore.py(The Shared Core):- Data accessors (
get_costs,get_gantt_data,get_jobs,get_fleet_state) extracted out of Vizor'sgenerate_viz_data(). ActorandRoleRBAC seams (owner,member,viewer), defaulting toLocalActor(owner).list_capabilities(actor)manifest pattern mapping active feature flags and roles into capability lists.- Single write chokepoint
_execute_write(action, actor, **params)handling dispatch, PR approval, and job termination while appending to.synlynk/events.jsonl. subscribe(event_types=None)for live event tailing.- Static
FeatureFlags.is_enabled(flag, tier)checks from.synlynk/config.json.
- Data accessors (
synlynk/tui.py(Terminal Surface):- Curses-based terminal interface (
synlynk tui) with 4 main panels: Fleet, Jobs, Costs, and Review. - Headless/pad-based render testing in
tests/test_tui_panels.pyrequiring no live terminal.
- Curses-based terminal interface (
synlynk/viz.pyRewire (Web Surface):- Web routes refactored to delegate data fetching to
uxcore.get_*()and write actions (dispatch/approve/kill) throughuxcorewrite functions.
- Web routes refactored to delegate data fetching to
synlynk/notifiers/slack.py(Reference BYOUX Consumer):- Minimal one-way Slack webhook notifier (
synlynk notify slack --webhook-url <url>) tailinguxcore.subscribe().
- Minimal one-way Slack webhook notifier (
docs/api/uxcore.md(BYOUX Public Library Interface):- Published public API documentation for external tools integrating with
uxcore.
- Published public API documentation for external tools integrating with
- Command Taxonomy & Plumbing:
- Added
tuitosynlynk/taxonomy.pyand regenerated CLI reference docs.notify slackis registered as a CLI subcommand but deliberately excluded from the taxonomy (it's a hook-triggered BYOUX consumer, not an orientation-gateway command).
- Added
Lesson from the Final Regression Pass
The feature work was implemented across 12 individual dispatched tasks, each with task-level unit tests. However, when running the full test suite (pytest) before merging, three cross-cutting regressions surfaced:
- A missing
'tui'entry insynlynk/taxonomy.pycaused taxonomy validation tests to fail. - An obsolete pre-rewire assertion in
tests/test_viz.pywas expecting directdispatch_agentcalls instead ofuxcoredispatch. docs/reference/commands.mdwas out of sync after registering the new CLI commands.
This served as an explicit reminder: task-scoped unit test passes are necessary but not sufficient — a full-suite regression run before branch completion remains mandatory.
Brainstorm visuals used
No dedicated HTML visual companion was created in docs/brainstorm/ for this specific UX 1.0 design spec. Prior art for Vizor's visual dashboard layout exists in docs/brainstorm/bs21-vizor/, which informed the original panel breakdown (Fleet, Jobs, Costs, Review) now mirrored 1:1 between the web interface and the new curses TUI.
What this achieved on the path to autonomy
In our overarching arc to position synlynk as "the OS for multi-agent development", two user interfaces hand-rolling separate views into workspace state created maintenance overhead and inconsistent policy enforcement.
By consolidating both the TUI and Vizor behind uxcore, any future agent execution, governance check, or telemetry filter implemented in uxcore instantly applies across all user-facing surfaces. Furthermore, the introduction of subscribe() and _execute_write creates a clean, event-driven extension seam for external tools, autonomous notification relays, and multi-agent orchestrators.
Next goalpost
With the BYOUX public interface established and validated by the reference Slack notifier, the next goalpost is exercising the uxcore seam with a second, non-Slack consumer (such as a webhook/relay consumer or the fleet-parity operability audit queued in project memory) to prove the abstraction cleanly generalizes beyond its first consumer.