WireFish Avalonia dogfood plan
Policies that keep the org coherent
name: WireFish Avalonia dogfood
overview: Bootstrap novolis-avalonia with reusable no-XAML controls and chrome-light layout primitives, then add a live-capture dogfood app in novolis-dogfooding that dogfoods Novolis.Transports.WireFish with a WireShark-inspired three-pane analyzer UI on stock Fluent theme.
todos:
- id: bootstrap-avalonia
content: Scaffold novolis-avalonia (slnx, Directory.*, Novolis.Avalonia.Layout + Controls, TUnit tests) status: completed
- id: shared-controls
content: Implement AnalyzerWorkspace, HexDumpView, TreeDetailsView, PacketTableView, FilterBar on Fluent theme status: completed
- id: wirefish-app
content: Add novolis-dogfooding/apps/WireFishViewer with Host+Avalonia bootstrap and MainWindow composition status: completed
- id: capture-bridge
content: Implement CaptureSessionService, UiPacketCaptureHandler, PacketRow/PacketDetailBuilder, lazy Start/Stop + BPF status: completed
- id: governance-docs
content: Add wave-9-wirefish-dogfood brief, update dogfooding apps README and novolis-avalonia README status: completed isProject: false
Context
| Asset | State |
|---|---|
| [`Novolis.Transports.WireFish`](d:\novolis\novolis-transports\src\Novolis.Transports.WireFish) | Live capture via SharpPcap; `DevicePacket` + extension helpers; `AddNovolisWireFish` + `IPacketHandler` dispatch |
| [`novolis-avalonia`](d:\novolis\novolis-avalonia) | Repo exists (submodule in dogfooding) but **no `src/` yet** — template README only |
| [`novolis-dogfooding`](d:\novolis\novolis-dogfooding) | Pattern: `apps/<App>/` with `ProjectReference` to `submodules/novolis-*` ([`DoomLite3D`](d:\novolis\novolis-dogfooding\apps\DoomLite3D) is the reference) |
| Prior samples | [`PacketScroller`](d:\novolis\bootstrap\scratch\Frank.WireFish\Samples\Frank.WireFish.PacketScroller) — terminal UI only; no GUI to port |
User choices: live-capture MVP (no offline .pcap yet); Fluent default theme with minimal custom chrome.
Target architecture
flowchart TB
subgraph dogfood [novolis-dogfooding/apps/WireFishViewer]
AppHost[Program + Generic Host]
MainWin[MainWindow]
UiHandler[UiPacketCaptureHandler]
Session[CaptureSessionService]
end
subgraph avalonia [novolis-avalonia]
Controls[Novolis.Avalonia.Controls]
Layout[Novolis.Avalonia.Layout]
end
subgraph transports [novolis-transports]
WireFish[Novolis.Transports.WireFish]
end
AppHost --> MainWin
AppHost --> WireFish
UiHandler --> Session
WireFish --> UiHandler
MainWin --> Controls
MainWin --> Layout
Session --> WireFishSeparation rule
- `novolis-avalonia`: domain-agnostic UI (split panes, hex dump, tree details, filter bar,
DataGridcolumn helpers). NoDevicePacket, SharpPcap, or WireFish types. - `WireFishViewer` app: capture session,
IPacketHandler, row models, protocol-tree building fromPacketDotNet, BPF/device wiring.
Follow the no-XAML convention from `novolis-templates` Avalonia solution and the Generic Host + `IHostedService` bootstrap from `Program.cs`.
Phase 1 — Bootstrap `novolis-avalonia`
Mirror standard Novolis repo layout (`frank-naming-and-structure.md`):
novolis-avalonia/
Novolis.Avalonia.slnx
src/Novolis.Avalonia.Controls/
src/Novolis.Avalonia.Layout/
tests/Novolis.Avalonia.Controls.Tests/
Directory.Build.props, Directory.Packages.props, global.json
.novolis/packages.jsonPackages (central versions in `Directory.Packages.props`):
Avalonia12.0.2 (align with templates)Avalonia.Desktop,Avalonia.Themes.Fluent,Avalonia.Fonts.InterMicrosoft.Extensions.Hosting(for optional shared host helpers later — keep Controls free of hosting if possible)
`Novolis.Avalonia.Layout` (generic)
| Type | Purpose |
|---|---|
| `AnalyzerWorkspace` | WireShark-like shell: top toolbar row, optional filter row, vertical split (packet list ~60% / bottom stack), horizontal split in bottom (tree ~50% / hex ~50%). Built with `Grid` + `GridSplitter`. |
| `ToolbarRow` | `StackPanel` of buttons + stretch filler + status text |
| `FilterBar` | Label + `TextBox` + Apply/Clear (events/callbacks only) |
`Novolis.Avalonia.Controls` (generic)
| Type | Purpose |
|---|---|
| `HexDumpView` | Mono `TextBlock` or read-only editor; accepts `ReadOnlyMemory<byte>` or `string`; 16-byte rows with offset column |
| `TreeDetailsView` | `TreeView` bound to `DetailTreeNode` (`Title`, `Children`, optional `Description`) |
| `PacketTableView` | Thin wrapper over Avalonia `DataGrid` with sensible defaults (virtualization on, single select, grid lines) |
| `DetailTreeNode` | UI-agnostic tree DTO in Layout or a tiny `Novolis.Avalonia.Abstractions` if needed |
Explicitly not in avalonia repo: packet parsing, BPF validation, capture device enumeration tied to SharpPcap.
Phase 2 — Dogfood app `WireFishViewer`
Add `novolis-dogfooding/apps/WireFishViewer/` and register in `Novolis.Dogfooding.slnx`.
Project references:
..\..\submodules\novolis-avalonia\src\Novolis.Avalonia.Controls\...
..\..\submodules\novolis-avalonia\src\Novolis.Avalonia.Layout\...
..\..\submodules\novolis-transports\src\Novolis.Transports.WireFish\...
..\..\submodules\novolis-messaging\src\Novolis.Messaging.Channels\...App structure:
apps/WireFishViewer/
Program.cs # Host + Avalonia lifetime (template pattern)
App.cs
MainWindow.cs # Composes AnalyzerWorkspace
Capture/
CaptureSessionService.cs # start/stop, applies WireFishOptions
UiPacketCaptureHandler.cs # IPacketHandler → UI thread
PacketRow.cs # list columns
PacketDetailBuilder.cs # PacketDotNet → DetailTreeNode + hex bytes
ViewModels/ (optional thin classes, no separate package)WireShark-inspired UX (MVP)
| Area | Behavior |
|---|---|
| Toolbar | **Start** / **Stop** capture; interface `ComboBox` (populate from `NetworkInterface.GetAllNetworkInterfaces()` — do not depend on internal `InterfaceProvider`) |
| Filter bar | BPF string → `WireFishOptions.BpfFilter`; apply on next start (document that running capture must restart to change filter) |
| Packet list | Columns: `#`, `Time`, `Source`, `Destination`, `Protocol`, `Length`, `Info` — map via existing [`DevicePacketExtensions`](d:\novolis\novolis-transports\src\Novolis.Transports.WireFish\DevicePacketExtensions.cs) (`GetSourceIPAddress`, `GetProtocol`, `GetPacketLength`, `GetPacketSummary` or a shorter summary helper in app) |
| Selection | Selecting a row fills `TreeDetailsView` (walk `Packet` link layers) + `HexDumpView` (`packet.Packet.Bytes`) |
| Status | Packet count, capture state, selected adapter; warning banner when Npcap missing (`AllowNoCaptureDevices = true` for dev machines) |
Capture lifecycle
WireFish starts capture in IHostedService at host start. For Start/Stop UX:
- Default:
AllowNoCaptureDevices = true; do not callAddNovolisWireFishuntil user clicks Start (register capture pipeline lazily), or - Register WireFish at startup but gate
PacketCaptureServicewith a customICaptureGate/ options flag.
Recommended: lazy registration on first Start — avoids capturing before UI is ready and matches WireShark mental model.
UiPacketCaptureHandler:
CanHandle→ alwaystrue(or filter in app if display filter added later)HandleAsync→Dispatcher.UIThread.Postappend to boundedObservableCollection<PacketRow>(cap e.g. 10_000 rows, drop oldest)- Never block capture thread on UI work
// Conceptual bridge (app code)
services.AddNovolisWireFish(b => b.AddPacketHandler<UiPacketCaptureHandler>(), o => {
o.CaptureAllDevices = false;
o.DeviceNames.Add(selectedInterface);
o.BpfFilter = bpf;
o.AllowNoCaptureDevices = true;
});Phase 3 — Governance and docs
Add brief `novolis-governance/docs/extraction-briefs/wave-9-wirefish-dogfood.md` modeled on `wave-9-doom-dogfood.md`:
- In: avalonia layout controls,
WireFishViewerapp, TUnit tests forPacketDetailBuilder/ row formatting - Out:
.pcapimport/export, display filters, follow-stream, dissector plugins, security scan panels - Done when:
dotnet run --project apps/WireFishViewerfrom dogfooding; README note for Npcap on Windows
Update `novolis-dogfooding/apps/README.md` with run instructions.
Refresh `novolis-avalonia/README.md` from template stub to describe Controls/Layout packages.
Phase 4 — Tests and CI
| Test | Location |
|---|---|
| `PacketDetailBuilder` layer tree for Ethernet/IP/TCP fixture bytes | `WireFishViewer` tests or small test project under dogfooding |
| `HexDumpView` formatting helper (pure string logic) | `Novolis.Avalonia.Controls.Tests` |
| WireFish extension smoke (existing) | already in transports |
Dogfooding CI: ensure dotnet build on solution includes new app; WireFish live capture remains manual (no Npcap in CI) — app must start and show empty state with warning.
Optional small WireFish library tweak (only if needed)
If lazy Start/Stop cannot be done cleanly without touching transports, add a minimal public hook in Novolis.Transports.WireFish (e.g. ICaptureController or expose start/stop on options) — prefer app-side host recycle first to avoid expanding transport API in MVP.
Risks and mitigations
| Risk | Mitigation |
|---|---|
| No Npcap on dev/CI | `AllowNoCaptureDevices`; empty UI + README |
| UI thread overload at high pps | Cap collection; optional pause UI updates while scrolling (future) |
| `InterfaceProvider` is internal | Use `System.Net.NetworkInformation` in app |
| Submodule drift | Document `git submodule update` for `novolis-avalonia` + `novolis-transports` |
Delivery order
- Scaffold
novolis-avaloniasolution + Layout/Controls + unit tests WireFishViewershell (mock rows) proving layout- WireFish +
UiPacketCaptureHandlerlive path - Governance brief + README + dogfooding slnx entry