Changelog

July 1, 2026 · View on GitHub

All notable changes to Snipdeck are documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

[1.1.0] - 2026-07-01

Added

  • Settings now has an Enabled checkbox for the global hotkey. Clearing it turns the hotkey off immediately and disables the shortcut-capture box; the choice persists across restarts, and the stored chord is kept so re-enabling restores it. The Reset button turns the hotkey back on and restores the Ctrl+Alt+S default.

Changed

  • Refreshed dependencies to their latest stable releases (Markdig, WebView2, Windows SDK build tools, Spectre.Console, the test SDK and the versioning tool).

Fixed

  • The release pipeline no longer fails to install the pinned Nerdbank.GitVersioning CLI when the hosted runner ships a newer build.

[1.0.4] - 2026-06-10

Changed

  • Refreshed bundled dependencies to their latest stable releases, including the Windows App SDK (2.2.0) and the Velopack updater (1.2.0).

Security

  • The release pipeline now publishes the snipdeck-importer .NET tool to NuGet.org using trusted publishing — short-lived OIDC credentials issued per run — in place of a long-lived API key.

[1.0.3] - 2026-06-08

Fixed

  • Installed (Velopack) builds crashed on launch with a blank window — the published app was missing its resource index (resources.pri), so the first screen failed with a XAML parsing error. The published build now ships the app's resources correctly, and launches as expected. Running from Visual Studio was unaffected.
  • The system tray menu's "Show Snipdeck" and "Exit" entries did nothing when clicked. They now work.

[1.0.2] - 2026-06-08

Added

  • The SnipCommand importer is now published to NuGet as a .NET global tool. Install it with dotnet tool install --global NextIteration.Snipdeck.Importer; the command stays snipdeck-importer. The release pipeline packs and pushes it to NuGet.org on each version tag.

[1.0.1] - 2026-06-08

Fixed

  • The list/content pages (Settings, Trash, Shared parameters, Tags, History) now share one layout with a consistent maximum content width — centred at wider window sizes and squashing when narrower — so every page's list uses the same horizontal band. History previously used a wider band, which made it drift right and clip at narrower widths. Each page's primary action (Save, Add parameter, Clear all) is aligned to its heading row, with the description beneath.

[1.0.0] - 2026-06-08

Added

  • Run a Snip, not just copy it. Snip cards gain a Run action beside Copy. Running walks the same parameter-fill flow, then shows a dry-run preview of the exact resolved command — alongside the shell and working directory it will execute under — before anything runs.
  • Live terminal with full fidelity. Runs execute under a real pseudo-terminal (ConPTY), so colours, spinners, progress bars and interactive prompts (e.g. Spectre.Console selection/confirmation prompts) all behave as in a real terminal. Output streams into an embedded terminal you can type into; a Cancel button stops the run, and an exit-code badge and elapsed time appear when it finishes.
  • Clean, searchable run history. Every run is recorded with a clean plain-text transcript — no escape codes, no cursor artefacts, no stacked spinner frames — plus the command, timestamp, duration and exit code. A new History view (next to Settings) lists runs newest-first with text search; open one to replay its output in full colour, or run it again.
  • Per-CLI execution settings. The CLI editor gains a shell (Command Prompt, Windows PowerShell, PowerShell, Bash, or a custom shell), an optional executable path and working directory (both with Browse…). All are optional and validated only at Run time, so a CLI stays useful for authoring before its tool is installed. A Snip can override its CLI's shell and working directory.
  • History settings. Configure how many runs to keep per Snip and the maximum captured output size (runs are truncated beyond it).
  • Acknowledgements. Settings → About lists the open-source projects Snipdeck is built on, each linking to its repository; the README carries the same list.

Added

  • Snipdeck branding. The app now ships with its own icon — a faceted green chevron — replacing the default .NET exe icon, the generated identicon in the system tray, and the blank window icon. The icon appears on the executable, taskbar, Start menu, Alt-Tab, window title bar (beside the app name), system tray and the Velopack installer. The README carries the new tile logo, and the source SVGs live in designs/.

Changed

  • The "Next Iteration" eyebrow on the Home hero now uses the primary text colour (white in Dark theme) instead of the muted secondary grey.

Fixed

  • Title-bar contents (hamburger, app icon and name, search box, CLI switcher) and the system caption buttons now share one vertical midline — the caption buttons previously hugged the top of the bar.

[0.1.0] - 2026-06-01

Added

  • Icon picker for tag icons. The Tags view now has a Choose… button beside each tag that opens a searchable grid of icons to pick from, so you no longer have to know a Segoe Fluent Icons code point. Search filters by name, keyword or code point, and the free-text field remains for pasting a glyph directly. The catalogue ships with 100 curated icons and is a plain JSON file (icon-catalogue.json) in the Snipdeck app-data folder — edit it to add or remove icons (the picker reflects the change the next time it opens), and your edits survive app updates. Delete it to restore the default set.
  • snipdeck-importer console tool. A new cross-platform .NET tool (tools/Snipdeck.Importer, installable as snipdeck-importer) imports command snippets from a SnipCommand export into a Snipdeck store. It auto-suggests each CLI from the command's first token, translates SnipCommand's inline [sc_choice] / [sc_variable] markup into Snipdeck {token} placeholders plus structured parameters, and defaults to a safe dry-run — --write backs the store up first, mints fresh identifiers, creates CLIs on demand and skips duplicates (--allow-duplicates to override). --store, --cli and --into control the target. It also de-duplicates parameters used by two or more snips in a CLI, promoting them to CLI-scoped shared parameters (Choice params match on their option set regardless of name/order, most common name wins; Text params match by name, most common default wins) — --no-share-parameters disables it. See tools/Snipdeck.Importer/README.md.
  • Move a snip to a different CLI. The snip editor now has a CLI selector, so a snip can be re-homed to another CLI (useful after a bulk import lands snips in a fallback CLI).
  • Discoverable importer in the first-run seed. A new first-run snipdeck-importer CLI sits alongside Examples, with snips that demonstrate the importer's own commands.
  • Redesigned Home page. A gradient hero banner heads the page, the CLI launcher uses landscape tiles (232×172) showing each CLI's description, and a segmented selector below switches between Most used, Recent and Favourites snips (drawn from every CLI) shown as a card grid.
  • CLI descriptions. A CLI can carry a short description, edited in the CLI editor and shown on its Home card. (Store schema is now v4; an older build refuses a v4 store rather than dropping descriptions.)
  • Tag icons in the navigation. Tags can carry a Segoe Fluent Icons glyph, shown beside the tag in the left navigation (new tags default to a tag glyph). A new "Tags" entry in the left-pane footer lets you set each tag's icon. Icons are nav-only — snip tag chips are unchanged. (Store schema is now v3; an older build refuses a v3 store rather than dropping tag icons.)
  • Shared parameter definitions. Define a parameter once and reuse it across snips, at two scopes: CLI-scoped (in the CLI editor — inherited by every snip under that CLI) and global (a new "Shared parameters" entry in the left pane — available to every snip across all CLIs). A snip's {token} resolves its definition by name with precedence snip-local → CLI → global; omit a parameter to inherit the shared one, or define it locally to override. Existing snips are unaffected. (Store schema is now v2; an older build refuses a v2 store rather than dropping shared definitions.)
  • Change the storage location. A "Change…" button on Settings → Storage location lets you pick a new folder for your snips. If the folder already contains a Snipdeck store it's adopted (your current snips are left where they are); otherwise your store and icons are copied there (the old folder is kept as a backup). The choice is confirmed first, and Snipdeck restarts to apply it — the storage path is read at startup, so restarting keeps everything consistent and avoids writing to the old location after the switch. If the automatic restart can't run, you're prompted to restart manually.
  • Rebindable global hotkey. The global hotkey is now editable from Settings: click the capture box and press a shortcut (at least one of Ctrl/Alt/Shift plus a key). The new binding registers and persists immediately; if the chord is already taken by another app, the previous binding is restored and a brief notice is shown. A "Reset" button restores the default (Ctrl+Alt+S). Previously the hotkey was display-only.
  • Markdown rendering for snip descriptions. A snip's description is now rendered as Markdown (headings, bold/italic, inline and block code, links, ordered/unordered lists) in the copy flyout, instead of being hidden. The Markdown is parsed in Core (MarkdownParser, backed by Markdig) into a UI-free model that the WinUI head maps onto native text; the editor labels the field as Markdown. A snip with a description but no parameters now opens the flyout too, so its description is always visible before copying.
  • Git-derived version numbers (Nerdbank.GitVersioning). Every build now stamps a real version (0.1.0-alpha.<git-height>+<commit>) into all assemblies from a single version.json, so the About page shows a meaningful version for dev and CI builds instead of the 1.0.0 compile-time fallback. Releases derive their version from NBGV rather than the tag string — see CONTRIBUTING.md for the nbgv tag release flow.
  • Trash. A "Trash" entry in the pane footer lists soft-deleted snips from across every CLI. Each can be Restored (returned to its CLI) or Deleted permanently (after confirmation). Previously, deleting a snip moved it to trash but it then vanished with no way to recover or purge it.
  • Configurable backup retention. Choose how many timestamped store backups to keep (default 20) from Settings → "Backups to keep". The count is honoured on the next write-triggered backup, with no restart required.
  • Delete a CLI. A "Delete CLI" action on the CLI view removes an empty CLI after confirmation. Deletion uses must-be-empty semantics: a CLI with visible (non-trashed) snips can't be deleted until those snips are removed. The CLI's icon asset and any leftover trashed snips are cleaned up with it.

Changed

  • Home, navigation and shared-parameters polish. The Home page leads with a full-bleed hero banner (drop Assets/HomeHero.png to supply the image), the CLI launcher is a horizontal carousel that overlaps the banner, and a centred pill selector switches between Most used / Recent / Favourites. The navigation pane toggle (hamburger) moved to the title bar (Home is the first nav item), and the footer destinations (Shared parameters / Tags / Trash / Settings) now show the selected indicator like the other nav items. Clicking Home also clears the search. Shared parameters — global and per-CLI (reached from the CLI view header) — now have a read-only card view with an edit modal, instead of inline editing; the Tags and Shared-parameters panes are left-aligned. Settings is grouped into "Appearance & behaviour" and "About"; the About expander shows the version and copyright, with links to clone the repo and file issues.
  • Shell layout: CLI switcher and search moved to the title bar. The CLI switcher and a snip search box now live in the custom title bar. Search is snip-only with name autocomplete, scoped to the selected CLI; each suggestion shows its CLI in a badge so identically-named snips are distinguishable, and choosing one filters the list to it. The left navigation now leads with Home and Documentation entries, followed by a Tags heading with an All entry and the scoped tags; the footer actions (Shared parameters, Tags, Trash, Settings) are left-aligned.
  • Polished cards, dialogs and destructive actions. Snip cards now size the Copy button to its content and group Edit/Delete immediately beside it. Destructive actions (Delete CLI, and the delete confirmations) use a subtle red treatment. Dialogs have rounded corners, all dialog buttons share rounded corners with more breathing room between them, and the snip editor is wider so its content fills the available space. The snip editor's "Command template" heading no longer inherits the monospace font.
  • JSON stores moved to System.Text.Json source generation. JsonSnipStore and JsonSettingsStore now serialise via a generated JsonSerializerContext instead of the reflection-based serializer, removing the IL2026 trim warnings. The on-disk format is unchanged (proven byte-identical by tests, with enum names pinned via [JsonStringEnumMemberName]), so existing stores keep loading. PublishTrimmed stays off: a trimmed WinUI publish still trips IL2104 on the WinAppSDK/WinRT/Jdenticon assemblies, which aren't trim-safe.

Fixed

  • Tag chips unreadable in Light theme. Tag chips on snip cards (and the parameter-type badge in Shared parameters) showed dark text on the accent fill in Light theme. They now use the on-accent text colour, readable in both themes.
  • Window caption buttons invisible in Light theme. The minimise, maximise and close glyphs were always white, leaving them barely visible against the light title bar. They now take a theme-appropriate colour, update when you switch theme, and follow the OS when the theme is set to System.
  • Tags view polish. The icon text box now shows a chosen icon by its friendly name (e.g. Settings) — or its hex code when the glyph isn't in the catalogue — instead of an unreadable box, and accepts a name as well as a code. The tag cards now match the Shared parameters, Trash and Settings cards (consistent card background and border), rather than a borderless grey fill.
  • Typing into a Text parameter in the copy flyout. A Text parameter's box cleared itself on every keystroke (and Copy never enabled), because the hidden Choice dropdown in the same row stayed two-way bound to the value and coerced it to null. Each parameter row now realises only the control its type needs, so typed values stick, the preview resolves, and Copy enables.

[0.1.0-alpha.1] - 2026-05-30

First packaged release. Cuts an alpha to exercise the release pipeline end-to-end and put the v1 feature set in front of real users.

Added

  • Unhandled-exception logging. Exceptions that previously vanished into WinUI's "Continue?" debugger dialog (and would have crashed the process outside the debugger) are now captured to %LOCALAPPDATA%\Snipdeck\logs\unhandled.log. The handlers cover XAML Application.UnhandledException, unobserved Task exceptions, and AppDomain.UnhandledException for completeness. IPathProvider gains LogsDirectory. The log rotates at 5 MB.

Fixed

  • Tray icon initialisation crash, take two (ArgumentException: Argument 'picture' must be a picture that can be used as a Icon.): once H.NotifyIcon could read the file (PR #15) it passed the bytes to new System.Drawing.Icon(stream, size), which only accepts ICO-format input. The tray service now wraps the identicon PNG in a minimal ICO container (modern Windows accepts PNG-in-ICO) and writes %LOCALAPPDATA%\Snipdeck\tray-icon.ico.
  • Tray icon initialisation crash (NullReferenceException inside H.NotifyIcon.ImageExtensions.ToStream(Uri)): H.NotifyIcon resolves TaskbarIcon.IconSource by reading BitmapImage.UriSource, but the tray service was loading the identicon via BitmapImage.SetSourceAsync from an in-memory stream — pixels populated, URI null. The service now persists the identicon to %LOCALAPPDATA%\Snipdeck\tray-icon.ico and loads the BitmapImage from that URI, which is the shape H.NotifyIcon expects.
  • First-run crash on startup (RPC_E_WRONG_THREAD / 0x8001010E): ShellViewModel.LoadAsync resumed on a thread-pool thread after loading the store and then mutated CliChoices, an ObservableCollection already bound to the CLI switcher. WinRT rejects cross-thread collection-changed marshalling. The await now stays on the UI thread.

Added — Phase 6: Settings page + Velopack updater

  • Settings page becomes editable. Theme switches live (System/Light/Dark apply immediately via IThemeApplierMainWindow's content tree). Close behaviour (Hide-to-tray vs Exit) persists to AppConfig as you change it. Global hotkey is shown read-only for now — rebinding lands later.
  • About expander is populated from real assembly metadata: name, InformationalVersion, and copyright (from Directory.Build.props).
  • Manual update check via IUpdateService (Velopack-backed, WindowsUpdateService). Points at the GitHub releases for this repo, catches dev-build / network-failure cases, and exposes a Check / Apply pair on the Settings page.
  • SettingsViewModel now lives in the DI container as transient — a fresh instance is resolved each time the Settings entry is clicked so reading config state is always current.

Added — Phase 5: Platform services

  • Global hotkey via Win32 RegisterHotKey. Default Ctrl+Alt+S; pressed anywhere brings the existing Snipdeck window to the foreground. WindowsHotkeyService subclasses the main window's WndProc with SetWindowSubclass to catch WM_HOTKEY. HotkeyModifiers map straight through to MOD_* constants (intentional alignment from Phase 1).
  • Tray icon via H.NotifyIcon. Left-click brings the window forward, right-click shows a context menu with Show Snipdeck and Exit.
  • Close-to-tray behaviour: when AppConfig.CloseBehaviour is HideToTray, AppWindow.Closing is cancelled and the window hides. The tray's Exit flips an internal flag and lets the next close pass through cleanly.
  • IFilePickerService abstraction (App-side WindowsFilePickerService) centralises file-picker setup. CliEditorDialog no longer pokes Win32 itself — it consumes the abstraction.
  • CLI cards now render uploaded icons (when Cli.IconRef is set), falling back to identicons when not. Identicon control gains an IconRef dependency property and resolves the absolute path via IIconAssetStorage.

Added — Phase 4: Authoring + parameter-fill flyout

  • Snip card actions are now live: Copy opens a parameter-fill ContentDialog (or copies the template directly when the Snip has no parameters), Edit opens a Snip editor, Delete soft-trashes after confirmation, the star toggles favourite.
  • Copying a Snip bumps UsageCount and LastUsedAt (drives the most-used list on Home).
  • New Snip / New CLI buttons on Home and the CLI view. Edit CLI button on the CLI view header.
  • SnipEditorDialog — title, command template (monospace, multi-line), description, tags (comma-separated), parameter rows with type (Text / Choice), default, and options. Add/remove parameters inline.
  • CliEditorDialog — name + icon picker. Picked images are normalised to a 256² centre-square PNG via WindowsIconNormaliser (Windows.Graphics.Imaging) and stored under <data>/icons/<cli-id>.png by IconAssetStorage.
  • ParameterFillDialog — one input per parameter (TextBox for Text, ComboBox for Choice), with a live preview of the resolved command and the Copy button disabled until the template is fully resolved.
  • New Core abstractions: IClipboardService, IIconNormaliser, IIconAssetStorage, IShellInteractions.
  • New Core view models: ParameterFillViewModel, ParameterInputViewModel, SnipEditorViewModel, ParameterEditorRowViewModel, CliEditorViewModel.
  • ShellViewModel gains CopySnipCommand, EditSnipCommand, DeleteSnipCommand, ToggleFavouriteCommand, NewSnipCommand, NewCliCommand, EditCurrentCliCommand, SelectCliCommand.
  • App-side implementations: WindowsClipboardService, WindowsIconNormaliser, WindowsShellInteractions.
  • Clicking a CLI card on Home navigates into that CLI (was: switcher-only).
  • 18 new Core unit tests cover the new view models and the command flow (clipboard write, usage bumping, soft-delete, favourite toggle, new-CLI-with-icon).

Added — Phase 3: Shell + read-only browse

  • ShellViewModel owns the cross-cutting shell state: CLI switcher choices, current search text, selected tag (with an "All" sentinel for clean filter-off semantics), and the active content view model.
  • HomeViewModel builds the home view's CLI cards (alphabetical, with snip counts) and the most-used Snips list (top 6 by UsageCount, then LastUsedAt desc; hidden when nothing's been used yet).
  • CliViewModel exposes the filtered Snip list for a single CLI, favourites bubbled to the top.
  • SettingsViewModel stub — populates the About expander; real settings UI arrives in Phase 6.
  • SnipFilter pure helpers: case-insensitive search across title / template / tags, tag filter, trash exclusion, DistinctTagsFor for the pane tag list.
  • IdenticonService (Jdenticon-net) — generates deterministic identicon PNG bytes seeded off Cli.Id so renaming a CLI doesn't change its icon.
  • ShellPage (WinUI): NavigationView with a custom pane header (AutoSuggestBox search + CLI switcher ComboBox), pane body tag list, pane footer Settings button, content area driven by a ShellContentTemplateSelector that picks the right DataTemplate based on the current content view-model type.
  • Custom user controls: Identicon (dependency-property-driven, lazy image load), CliCard (identicon + name + snip count), SnipCard (title, monospace template preview, tag chips, favourite star, disabled Copy/Edit/Delete buttons with "Phase 4/5" tooltips).
  • Settings page stub uses SettingsCard / SettingsExpander with About as the last expander; About shows app name, copyright, and version (the version string falls back to the assembly's InformationalVersion until Phase 6 wires Nerdbank.GitVersioning).
  • MainWindow now hosts the ShellPage in its content row; the custom title bar and Mica backdrop carry over from Phase 2.
  • Converters: BoolToVisibilityConverter, CountToVisibilityConverter.

Added — Phase 2: App lifecycle skeleton

  • Explicit Program.cs entry point that runs the Velopack hook → initialises WinRT COM wrappers → checks single-instance via Windows App SDK AppInstance.FindOrRegisterForKey("snipdeck") and redirects activation to the primary instance → starts WinUI with a DispatcherQueueSynchronizationContext.
  • Bootstrap.cs builds the DI container (Microsoft.Extensions.DependencyInjection): loads AppConfig from the settings store synchronously, resolves the snip-store and backup directories (config value or default), and registers every Core service plus MainWindow.
  • App-side platform services: WindowsPathProvider (paths rooted at %LOCALAPPDATA%\Snipdeck), WinUiDispatcher (lazy-captures the UI thread's DispatcherQueue on first use).
  • Core abstractions: IPathProvider, IDispatcher.
  • MainWindow shell: Mica backdrop, ExtendsContentIntoTitleBar with a custom draggable title bar showing the app name, theme application from AppConfig (Light / Dark / System).
  • First-run seed: if the snip store is empty when the app launches, the Examples CLI is written via ISnipStore before the window appears.
  • Activation redirect from a secondary instance brings the primary instance's window back to the foreground.
  • Microsoft.Extensions.DependencyInjection (10.0.8) added to centralised package versions.

Added

  • Repository scaffold: Snipdeck.Core (net10.0, UI-free), Snipdeck.App (WinUI 3, net10.0-windows), Snipdeck.Core.Tests (xUnit).
  • Apache 2.0 licence.
  • README and changelog.
  • Core domain models: Cli, Snip, Parameter (Text / Choice), Tag as a string list, root SnipStoreDocument with SchemaVersion.
  • SubstitutionEngine — replaces {placeholder} tokens against a value dictionary and returns both the resolved text and the list of unresolved tokens in first-appearance order. Only [A-Za-z_][A-Za-z0-9_]* is treated as a token, so JSON braces and other literal braces pass through.
  • ISnipStore / JsonSnipStoreSystem.Text.Json-backed store with temp-file-then-rename atomic writes, schema-version guarding, and a single semaphore protecting concurrent access.
  • ISettingsStore / JsonSettingsStore — application settings stored separately from the snip store, with defaults (Theme = System, CloseBehaviour = HideToTray, hotkey = Ctrl+Alt+S) applied when the file is missing.
  • IBackupService / BackupService — copies the snip store to a timestamped filename on demand, prunes to the configured retention (default 20), and exposes a newest-first listing.
  • IClock / SystemClock for testable time.
  • ExamplesSeed — first-run seed producing one "Examples" CLI with a handful of representative Snips (Text + Choice parameters, tags, a favourite).
  • GitHub Actions CI workflow: builds Core on Ubuntu, builds the full solution on Windows, runs Core tests on both.
  • GitHub Actions release workflow: tag-triggered (v*.*.* stable, v*.*.*-* pre-release), publishes the app, packs with Velopack, and attaches the artefacts to a GitHub Release.
  • .editorconfig taken verbatim from StuartMeeks/NextIteration.SpectreConsole.SelfUpdate. Enforces brace style, using ordering, naming conventions, and analyser severities.
  • Directory.Build.props at the repo root: shared Authors ("Stuart Meeks"), Company ("Next Iteration"), copyright, nullability, implicit usings, latest LangVersion, and TreatWarningsAsErrors=true + EnforceCodeStyleInBuild.
  • Directory.Packages.props at the repo root for Central Package Management (ManagePackageVersionsCentrally=true, CentralPackageTransitivePinningEnabled=true).
  • CONTRIBUTING.md covering prerequisites, the non-negotiables, the Core / App boundary discipline, the release process, and how to ask for direction when something's genuinely ambiguous.

Changed

  • All Core source converted to block-scoped namespaces, collection expressions ([]), and a GeneratedRegex-backed token regex to comply with the editorconfig under TreatWarningsAsErrors.
  • JsonSnipStore, JsonSettingsStore, and BackupService now implement IDisposable to release their internal semaphores (CA1001).
  • Snipdeck.App.csproj no longer carries <Nullable> (now inherited from Directory.Build.props).
  • Bumped test-project dependencies to latest stable: coverlet.collector 6.0.4 → 10.0.1, Microsoft.NET.Test.Sdk 17.14.1 → 18.6.0, xunit.runner.visualstudio 3.1.4 → 3.1.5.