Phase 1: core telnet/ANSI/session pipeline - #1
Merged
Conversation
…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
…r + Session.StateChanged
…act) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
…ck .idea Co-Authored-By: Claude Opus 4.8 <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
…fakes Co-Authored-By: Claude Sonnet 4.6 <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
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
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
…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
…ection and /settings route
… with /settings route
…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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/StyledSegmentvalue types andAnsiParser(SGR incl. 16-colour, bright, xterm-256, attributes; non-SGR CSI stripped; truecolor38;2;...consumed gracefully without corruption).Connection/—ConnectionState+TelnetConnectionwrapping TelnetNegotiationCore 2.5.0 over aTcpClient(connect/send/receive surfaced as events).Sessions/—Session(parse to scrollback, raisesLineAppended) +ScrollbackLine.LoopbackServerTCP double.Quality
net10.0-androidapp head) builds at 0 warnings underTreatWarningsAsErrors.�escapes in source, connection resource cleanup).Tracked follow-ups (Phase 2 — UI/rendering)
38;2;r;g;b) RGB rendering (Phase 1 only prevents corruption).Sessionscrollback thread-safety + documented threading contract (events fire on the network thread).Session.StateChangedevent for the UI shell.Design spec and plan live under
docs/superpowers/.🤖 Generated with Claude Code