Skip to content

Phase 1: core telnet/ANSI/session pipeline - #1

Merged
HarryCordewener merged 61 commits into
masterfrom
feat/core-pipeline
Jun 30, 2026
Merged

Phase 1: core telnet/ANSI/session pipeline#1
HarryCordewener merged 61 commits into
masterfrom
feat/core-pipeline

Conversation

@HarryCordewener

Copy link
Copy Markdown
Member

Phase 1 — Core pipeline

The device-independent core of SharpClient: telnet connect/send/receive over TCP, ANSI/xterm-256 colour parsing into a styled-segment model, and a Session that ties them into an observable scrollback. Fully unit/integration tested with TUnit, no Android device required.

What's here

  • Rendering/AnsiColor / TextStyle / StyledSegment value types and AnsiParser (SGR incl. 16-colour, bright, xterm-256, attributes; non-SGR CSI stripped; truecolor 38;2;... consumed gracefully without corruption).
  • Connection/ConnectionState + TelnetConnection wrapping TelnetNegotiationCore 2.5.0 over a TcpClient (connect/send/receive surfaced as events).
  • Sessions/Session (parse to scrollback, raises LineAppended) + ScrollbackLine.
  • Integration tests run against an in-process LoopbackServer TCP double.

Quality

  • 16/16 tests passing; full solution (incl. the net10.0-android app head) builds at 0 warnings under TreatWarningsAsErrors.
  • Built via subagent-driven TDD: each task spec+quality reviewed, plus a whole-branch review whose findings were fixed (truecolor degradation, visible escapes in source, connection resource cleanup).

Tracked follow-ups (Phase 2 — UI/rendering)

  • Full truecolor (38;2;r;g;b) RGB rendering (Phase 1 only prevents corruption).
  • Session scrollback thread-safety + documented threading contract (events fire on the network thread).
  • Session.StateChanged event for the UI shell.

Design spec and plan live under docs/superpowers/.

🤖 Generated with Claude Code

HarryCordewener and others added 30 commits June 29, 2026 23:48
…back tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
…scapes, connection cleanup

- AnsiParser: consume 38;2/48;2 truecolor sequences with graceful degradation to Default (Fix 1)
- AnsiParser + tests: replace literal 0x1b ESC bytes with \e escape in all source files (Fix 2)
- TelnetConnection.ConnectAsync: dispose _client on partial failure, guard re-entrant connect (Fix 3)
- TelnetConnection.DisconnectAsync: await _readTask with teardown-exception swallowing (Fix 4)
- TelnetConnectionTests + SessionTests: hold ServiceProvider in field, dispose in [After(Test)] (Fix 6)
- AnsiParserTests: add TruecolorForegroundDoesNotCorruptBackground and TruecolorBackgroundIsConsumed (Fix 1)
- Fix trailing newline in AnsiParserTests.cs (Fix 7)

Tests: 16/16 passing, 0 warnings
- docs/design/: prototype (SharpClient.prototype.html), 24 screenshots, and
  design-tokens.md (Phosphor palette, ANSI 16-colour map, StyledSegment render
  contract, 5-state connection palette, screen inventory)
- spec: visual-design section, extended ConnectionState (+Reconnecting/Error),
  Session.StateChanged, interface-based architecture (§5.1), interface-based +
  bUnit testing (§8), updated phasing

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
…foundation done, web preview host next)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
New project src/SharpClient.Data (net10.0 classlib) referencing
SharpClient.Core. Adds Microsoft.EntityFrameworkCore.Sqlite 10.0.9
(with a targeted NuGetAuditSuppress for GHSA-2m69-gcr7-jv3q, the only
published version of SQLitePCLRaw.lib.e_sqlite3 2.1.11).

AppDbContext configures the World→Character→TriggerRule/AliasRule graph
with shadow FKs (WorldId, CharacterId) for rules shared between both
principal types. WorldStore implements IWorldStore; UpdateWorldAsync uses
a clear-tracker + delete-first + insert-second two-commit strategy to
avoid a SQLite UNIQUE constraint violation when replacing a same-PK row.

Project added to SharpClient.slnx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
8 TUnit tests on a per-test temp SQLite file cover: Add+Get full graph,
TriggerRule/AliasRule/ConnectSecretKey round-trips, Update (rename +
add/remove character), Delete + orphan-row cascade check. All 8 pass;
existing SharpClient.Tests (49) and UI.Tests (2) suites unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
Implements ISessionHistory using Microsoft.Data.Sqlite raw SQL on the same
db file as AppDbContext. FTS5 virtual table; user query sanitised by wrapping
each token in double-quoted FTS5 phrase literals (embedded " doubled).
Sequence derived from FTS5 implicit rowid. 16/16 Data.Tests pass; 49 core
tests still green; 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
…ansaction

Prevents permanent data loss if the re-insert fails after the delete commits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
Pure-logic engines for the declarative trigger and alias systems.
TriggerEngine parses ANSI lines, matches Substring/Regex rules, and
accumulates Send/Notify actions; Highlight applies a whole-line foreground
recolour (v1 simplification). AliasEngine finds the first enabled regex
alias and substitutes $0–$9 capture groups. Both guard against malformed
patterns (catch + skip, 100 ms timeout). 26 new TUnit tests, 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
Adds src/SharpClient.Web — a Blazor Web App (Server interactivity) that
references SharpClient.UI and SharpClient.Core and renders the existing
OutputView component with a realistic ANSI sample feed styled per the
Phosphor design tokens.

Key changes:
- SharpClient.Web.csproj: references UI + Core; removes redundant props
  (inherited from Directory.Build.props)
- App.razor: Google Fonts (JetBrains Mono, Space Grotesk); Bootstrap removed
- wwwroot/app.css: full design-token CSS variables, terminal body backdrop,
  output container (.sc-output/.sc-line matching OutputView's class names),
  13px JetBrains Mono / 1.58 line-height / white-space:pre-wrap
- Components/Pages/Home.razor: 21-line sample feed via AnsiParser.Parse —
  covers bold, 16-colour fg (31/92/33/34/35/36/93/91/97), xterm-256
  (208/118/87/135), underline, inverse, plain and blank lines
- Layout stripped to bare @Body; Counter/Weather/NavMenu deleted
- Project added to SharpClient.slnx

Build: 0 warnings, 0 errors (net10.0, TreatWarningsAsErrors=true)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
…el.CloseAsync

Session ctor gains optional characterName/worldName default params keeping existing
call-sites compiling. FakeSession updated with settable identity props.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
SessionTabs renders tab strip with state-coloured dots and × close button.
InputBar binds to SessionsViewModel.Input with disabled-aware Send.
SessionScreen composes all three, subscribes to Vm.Changed for live updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
…sion

UiFakeSession uses explicit event accessors to satisfy ISession without CS0067.
SessionTabsTests: tab count, error-dot colour, active-tab class.
InputBarTests: disabled state, enabled on input, click-send delivers command.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
DemoSession implements ISession with seeded ANSI scrollback; SendAsync echoes
input as a new scrollback line. Program.cs registers SessionManager + SessionsViewModel
as singletons and seeds Vesper@Sindome (Connected/rich feed), Thorne@GrapevineMUD
(Connecting), Doran@BatMUD (Error). Home page renders full SessionScreen component.
app.css gains Phosphor-themed tab-bar, output-area, input-bar, and empty-state styles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
HarryCordewener and others added 25 commits June 30, 2026 03:19
…tocolPanel

- DemoSession: overrides NegotiationLog (TTYPE/NAWS/MCCP2/CHARSET/MSSP rows)
  and GmcpLog (Char.Vitals + Room.Info) with realistic static sample data
- Program.cs: register ProtocolPanelViewModel as singleton
- Home.razor: inject ProtocolPanelViewModel and pass to SessionScreen

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
…Android

Add MauiAppStorage, MauiSecretStore, MauiNotifier (stub), and
TelnetSessionLauncher to SharpClient.App/Services/. Register all
services in MauiProgram.cs: AddTelnetClient(), IAppStorage, ISecretStore,
INotifier, AppDbContext (transient), IWorldStore, ISessionHistory,
SessionManager/ISessionManager (singleton), SessionsViewModel,
ProtocolPanelViewModel, WorldManagerViewModel (transient), and
ITelnetConnectionFactory/TelnetConnectionFactory.

Add ITelnetConnectionFactory + TelnetConnectionFactory to SharpClient.Core
to work around a Roslyn metadata-loading failure for ITelnetInterpreterFactory
under net10.0-android (Stateless assembly version mismatch prevents Roslyn
from resolving TNC types in the MAUI Android TFM; Core compiles under plain
net10.0 where TNC types are fully accessible).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
…kens

Replace the default MAUI Blazor template pages with real SharpClient UI:
- Home (/) renders SessionScreen with injected SessionsViewModel +
  ProtocolPanelViewModel from the RCL
- /worlds renders WorldManager with injected WorldManagerViewModel
- Counter.razor and Weather.razor removed
- _Imports.razor adds SharpClient.Core.* and SharpClient.UI.Components usings
- MainLayout.razor replaced with sc-nav bar (Session / Worlds links)
- NavMenu.razor emptied (nav now in MainLayout)
- wwwroot/app.css replaced with full design-token CSS from SharpClient.Web
- wwwroot/index.html adds JetBrains Mono + Space Grotesk Google Fonts links
  and drops bootstrap.min.css reference (no longer needed with sc-* tokens)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
…t leak

A: Add OpenEditWorld() method and Edit button on each world row in
   WorldManager.razor (stopPropagation so expand-toggle is not triggered).
B: DeleteWorldAsync now iterates world.Characters and removes each
   ConnectSecretKey via _secrets.RemoveAsync before deleting the world,
   mirroring DeleteCharacterAsync behaviour.
…rets, UpdateAlias tests

C: Tests for UpdateWorldAsync (rename persists, UpdateCount increments),
   UpdateCharacterAsync (secret key is reused not regenerated, value updated),
   and DeleteWorldAsync (secrets removed for all characters).
E: UpdateAliasAsync test - alias replaced in-place + store UpdateCount
   increments, mirroring the existing UpdateTriggerAsync test pattern.
Add 'if (_world is null) return;' at the start of all eight mutation
methods (Add/Update/Delete/Toggle Trigger and Alias). Toggle methods
now also return early if the rule id is not found, avoiding a spurious
UpdateWorldAsync call.
…ce leak (item F)

Wrap the post-connect SendAsync call in try/catch; on any exception
DisposeAsync the session before rethrowing so the TelnetConnection is
not leaked when the connect string send fails.
…tem G)

display:contents made min-height:100vh a dead rule and caused the
.sc-scanlines::before overlay to be cross-browser-fragile (some browsers
skip pseudo-elements on display:contents elements). Changed to display:block
so min-height:100vh and ::before work reliably.
Also added the missing .sc-shell, .sc-glow and .sc-scanlines::before rules
to the App (MAUI) CSS which previously had none of these.
…cols var (item H)

Add '(font auto-sizing — coming soon)' annotation to the Min columns slider
label so users know it is a forward-looking passthrough. RootStyleVariables
now emits --min-cols:{MinColumns} as a CSS custom property for future use.
No JS-interop auto-size formula added (deferred per spec).
…item I)

The connect string is a credential; masking it prevents shoulder-surfing
and browser autofill misidentification. Blank-to-keep-existing behaviour
is unchanged.
J: Remove the dead <div class='status-bar-safe-area'> from index.html
   (its CSS was already removed).
K: Delete NavMenu.razor and NavMenu.razor.css — the file contained only
   a comment noting navigation is handled by the sc-nav bar; nothing
   references it.
…em L)

If the stored accent is not in s_accentMeta (e.g. from an old config or
manual edit), clamp it to '#9b7ed4' (default purple) and persist the
correction so --acc2/soft/line custom properties don't desync.
Test: UnknownAccentIsClampedToDefaultOnConstruction verifies the clamp,
the pref correction, and that RootStyleVariables contains no stale value.
…elds (item M)

Documents that _scrollback, _negotiationLog, and _gmcpLog are not
thread-safe and that LineReceived/protocol events may fire on the
network thread; UI consumers must marshal. Adds a TODO for future
concurrent-access guard.
Phase 2: UI foundation → feature-complete MUSH client (web preview + Android)
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.

2 participants