Phase 2: UI foundation → feature-complete MUSH client (web preview + Android) - #2
Merged
Merged
Conversation
- 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
Co-Authored-By: Claude Opus 4.8 <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
…nders in colour DemoSession/DemoSessionLauncher were missing the ESC prefix entirely; Program.cs used raw ESC bytes (functional but invisible in diffs). Normalised all to backslash-u-001b. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
…ssion wiring - GmcpMessage + NegotiationEvent records in Connection namespace - ITelnetConnection: GmcpReceived + NegotiationReceived events - TelnetConnection: AddDefaultMUDProtocols with GMCP + MSSP callbacks - ISession: NegotiationLog/GmcpLog/ProtocolChanged as default interface members - Session: accumulates both logs, dedupes GMCP by Package, fires ProtocolChanged - FakeTelnetConnection: EmitGmcp + EmitNegotiation test helpers - SessionProtocolTests: 6 tests all passing (89 total) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
…nScreen toggle
- ProtocolPanelViewModel: tracks active session NegotiationLog/GmcpLog,
re-raises manager.Changed + session.ProtocolChanged as single Changed event
- ProtocolPanel.razor: shows negotiation rows (key+detail) and GMCP packages
(package name + JSON pre); stable CSS classes for testability
- SessionScreen.razor: optional ProtocolVm parameter + { } toggle button in
header; panel rendered below output when visible; existing tests unaffected
- ProtocolPanelTests: 6 bUnit tests (17 UI tests total, all passing)
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.
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 2 — UI Foundation → feature-complete client
Builds on Phase 1 (PR #1). Stacked on
feat/core-pipeline— retarget tomasteronce #1 merges. Everything is interface-based and unit/integration tested with no device required.What's here
ITelnetConnection/ISession/ISessionManager, platform abstractions (ISecretStore,IAppStorage,INotifier,IPreferences),ISessionLauncher,ITelnetConnectionFactory, domain models (World/Character/TriggerRule/AliasRule). ExtendedConnectionState(+Reconnecting/Error) +Session.StateChanged.AnsiPalette(design 16-colour + xterm-256),SegmentStyle(StyledSegment→CSS render contract from the approved design).SessionManager(tabs) +SessionsViewModel;SessionTabs/InputBar/OutputView/SessionScreenRazor components.WorldManagerViewModel+WorldManager/WorldEditor/CharacterEditor; Connect flow viaISessionLauncher; secrets viaISecretStore(keyed, never in plaintext).SharpClient.Data): EF Core SQLiteWorldStore(transactional update) +SessionHistoryfull-text search via SQLite FTS5.TriggerEngine+AliasEngine(regex-guarded) + a triggers/aliases editor (/worlds/{id}/rules).ProtocolPanel(per-session debug drawer).IPreferences, applied live as CSS variables.SharpClient.Web(Blazor Server preview — view the UI on Linux with no Android SDK) and the realSharpClient.AppMAUI Blazor Hybrid Android app wired with real platform services (SecureStorage, FileSystem, telnet launcher).Quality
net10.0-androidhead build at 0 warnings underTreatWarningsAsErrors.Known follow-ups (non-blocking, tracked)
DeleteWorldAsyncsecret cleanup uses the in-memory cache;TelnetSessionLaunchercould also dispose on aConnectAsyncthrow.WebPreferencesis in-memory (no cross-reload persist); MinColumns→auto-font-size needs JS interop (slider labelled "coming soon").🤖 Generated with Claude Code