Skip to content

Releases: djwinston/wikeloContractor

0.9.8

Choose a tag to compare

@github-actions github-actions released this 26 Aug 23:37
b9b9d68

What's Changed

fix: a gathered item vanished from the plan, taking its overlay pin with it

GatheringPlan.Build dropped every fully covered item: the tab answers "what do I
still need", and an item needing nothing is not an answer to it. What that
reasoning missed is that the same list is the second place overlay pins are
made. Counting the last unit of a pinned item removed its row, so the only place
that pin could be unpinned from went with it, while the budget counter went on
reporting 10/10 for a pin the player could no longer see. The Inventory page was
the only way back to it — and there was no signal that this had happened, which
is why it read as missing data rather than as a rule working as written.

Covered items now stay, carrying GatheringItem.IsCovered, and the tab gains its
own All / Gathered / Not gathered combo so the shopping list is one selection
away. Deciding which rows exist stays in the model; presenting the two states
differently is the page's job.

The gathered card borrows the completed catalog row's treatment wholesale —
CompletedRow{Accent,Wash}Brush, a 2px left accent and a fade-out wash — because
it is the same statement: this line is done. Same keys rather than new ones, or
the app ships two greens for one meaning. The accent is reserved transparent on
every card, so becoming gathered swaps colours without moving content, and it
lives on an inner Border so it does not have to share a BorderBrush with the
card's outline.

The tab badge moves from Gathering.Count to a new OutstandingCount. With covered
rows in the list the row count would sit still while the player fills their
hold, which is the opposite of what a "what to still gather" badge is for.

SyncGathering folds GatheringRowViewModel.Update's return value into one
coverageMoved flag and refreshes the collection view only when a row actually
changed sides and a filter is selected. Insertions and removals reach the view
on their own; refreshing unconditionally would raise a Reset on the ~30x/s
held-hotkey path, which is the churn that method exists to avoid.

The filter and the pin budget are now shown for as long as there is a plan at
all, not only while something is short: a finished plan is exactly when the
gathered cards are all there is, and their pins still need a counter and a way
off.

refactor: the favourite star and completion toggle were written twice, and had drifted

Both are rendered by the catalog row and the contract detail page, and each was
written separately on the two. The copies had already diverged: the detail
page's toggle showed Checkmark24 for "not done yet" while the row showed
Circle24, so the same state read as two different things depending on which
screen you reached it from — and Checkmark24 is the glyph docs/design-system.md
reserves for the COMPLETED badge, so the detail page was saying the opposite of
what it meant. Nobody broke this; the second copy was simply written on a
different day. That is the whole argument for the shared dictionary.

Both now live in Chips.xaml as FavoriteStarStyle and CompletionToggleStyle, with
x:Shared="False" for the reason already spelled out on PinButtonStyle:
IconElement derives from FrameworkElement and {ui:SymbolIcon} is evaluated once
per Style, so a dictionary-level style without the flag hands the same icon
element to every button.

A style's contract is its DataContext. These bind IsFavorite / IsCompleted and
their commands by name, which ContractCardViewModel exposes directly; the detail
page scopes each button with DataContext="{Binding ViewModel}" so the same names
resolve there. Keeping the two view models' member names aligned is the price of
one control instead of two, and it is the cheaper side of that trade.
ContractDetailViewModel.CompletedButtonLabel is gone with it — the style's
trigger carries the label, so the glyph and the word cannot disagree.

Geometry stays with the caller, as it already does for the pin trio: a local
value beats a style setter, so the row keeps its compact ghost button and the
detail page its full-size one, and only the glyph pair, the label and the
command are shared.

docs/design-system.md gains a "Shared controls" section stating the rule the
above is an instance of — if two screens render the same thing it is built once,
and a second copy is a review finding — with the decision ladder (style vs
DataTemplate vs Views/Controls vs a model), a table of every shared control and
the DataContext each expects, and the x:Shared trap that comes with extracting
one. The paragraph claiming every other Icon setter sits inline in a
DataTemplate is no longer true and is corrected. CLAUDE.md carries the same rule
next to the Chips.xaml entry.

Closes the Phase 3.6 "shared controls" item, audited rather than assumed: the
progress bar and the COMPLETED chip were already shared as specified, three
deviations were deliberate (Circle24 over Checkmark24, Button over ToggleButton,
and ui:CardControl superseded by the dense-list catalog), and the two above were
the only ones never centralised. All five outcomes are recorded in PLAN.md.

Two other plan decisions ride along. The blueprint glyph, the last icon left
open, is Ribbon24 — on the detail page's Blueprints chip only, since the catalog
card's "BP · name" chip has no room for one. Not Reward24 from the same family:
the Rewards section sits directly below those chips and a blueprint is not one
of them. The code had been on Molecule24, the plan on ChannelShare16 and the
docs on a third value, because the decision was never actually taken.

And the catalog's card-grid view toggle is dropped at the user's request: the
dense list already fits every chip of every contract without truncation, so the
toggle buys a second layout to maintain and a persisted setting to migrate in
exchange for nothing the list cannot show. The card grid did get built, as the
Favorites gathering tab, where short and numerous rows earn it.

refactor: the XP label had leaked into the model it exists to keep out

The UI says XP, the domain model says reputation, and the whole point of that
split is that if the game ever renames what it shows, only the resource strings
change. ReputationStatus.TotalXp and ReputationLevels.Compute(int totalXp) were
named after the label while every neighbour was not — ICompletionService.
TotalReputation, WikeloContract.ReputationAmount, MissionDto.ReputationAmount —
and the record's own XML doc read "accumulated Wikelo reputation" the whole
time. Renamed to TotalReputation.

The localization strings were already correct in both dictionaries, which is why
this plan item looked done and was not: nothing user-visible changes here.

Two decisions the item left open are now taken and written down rather than
rediscovered later.

A contract that awards nothing keeps showing "+0 XP". The API sends an explicit
null for top-rank trades and 12 of the 67 contracts in the 4.9.0 catalog are in
that group, so it is a real state, not a parse failure — and hiding the badge
would make "awards nothing" and "has not loaded yet" look identical. The "+0 XP"
in design prototype 3a was placeholder data and never bore on this.

The rank banner's progress bar now wears ReadinessBarStyle. It was declaring
Height="5", Maximum, Minimum and alignment inline: not a stray hardcoded height
but a fourth copy of the shared meter, one pixel off the SizeProgressBarHeight
token every other bar uses. The style is named for its first use, so its comment
and docs/design-system.md now say what it actually is — the app's 0..1 meter,
rank banner included.

Closes the last open item of Phase 3.6.

add: wiki and cstone links on the item page, built from one uuid per guide

The last open item of Phase 3.5 asked for two things — a wiki link where the API
has one, and a per-item deep link into the cstone Finder "if it supports a query
URL". The investigation collapsed them into one.

The finder has no name search in its URL at all: its inline script never reads
the query string (?search= is accepted and silently ignored) and a name in the
path redirects to the home page. Its only per-item address is /Search/{guid} —
and that GUID is the game's own item UUID, the same value the wiki API returns.
Checked against an item, an armour piece, a commodity and a vehicle.

The wiki half turned out to rhyme. The API's web_url is
api.star-citizen.wiki/items/{slug}, which is a real player-facing page —
description, stats, images, crafting — not a developer view, and the same page
answers to the UUID. So one identifier yields both links, and the guides record
that identifier rather than two ready-made URLs: the shapes belong to sites we
do not control, so they live once in Models/ItemLinks instead of ~95 times in
Markdown.

94 of the 95 guides were seeded from the API's exact-name search, comparing the
returned name before accepting it — real values, none typed by hand, since a
wrong digit here is a button that opens someone else's item. Tungsten has no API
entry under that name and simply shows no buttons. The three ATLS variants carry
vehicle: true: the wiki files vehicles in their own namespace and
/items/{vehicle-uuid} only redirects. The finder needs no such flag.

None of this is app code. The seeding was a one-off authoring pass; the app
never looks an item up by name at runtime, so the Where to Find page stays the
offline reference it was built to be.

The buttons are named for where they go, not for what will be there. "Where it
is sold" was the first label and it was wrong: the finder lists shops only for
things that are sold, and most of this corpus is mission loot — the Ace
Interceptor Helmet drops from Ace patrol missions and has no shop entry, so the
label promised a list that does not exist. It is "Open on cstone" now, in a pair
with "Open on the wiki", and the tooltip carries the nuance the label should not.

The shared shop-purchase fragment still points at the finder's root. Guide prose
has no per-item su...

Read more

0.9.7

Choose a tag to compare

@github-actions github-actions released this 05 Aug 21:42
4018250

What's Changed

add: notification-area icon, with minimize to tray (Phase 5)

The window is not what the player looks at while playing — the overlay is — so
the shell spends most of a session out of the way. A NotifyIcon in MainWindow.xaml
carries a three-item menu: open the window, toggle the in-game overlay (the check
mark reads the HUD's own state), quit. Minimize to tray is off by default and read
at the moment of the minimize, so the Settings switch needs no restart.

ViewModels/TrayViewModel holds the menu and the minimize rule over the
Services/ITrayHost seam that MainWindow implements and hands to it, which is what
makes E2E/TrayScenarios provable without a window. Same window-seam idea as
OverlayService/IOverlayWindow, but the coordinator is a view model because every
decision here is a menu item; the day the tray grows one that is not, split a
TrayService out.

Exit closes the shell rather than calling Application.Shutdown(). MainWindow.OnClosed
stays the single exit trigger and the only path that reaches StopAsync, which flushes
the inventory store — a menu item that shut down directly would drop the counts edited
in the last seconds in game.

Two failures this feature can produce that no other can, both guarded:

  • Hide() removes the window from the taskbar AND from Alt+Tab, so hiding into a
    notification area with no icon of ours would leave Task Manager as the only way
    back. Hiding is gated on ITrayHost.IsTrayAvailable, read fresh each time; with no
    icon the window just minimizes normally. Same guard OverlayService.Initialize makes
    for a click-through HUD whose unlock hotkey never registered.
  • An Explorer restart rebuilds the notification area and takes every icon with it.
    Wpf.Ui.Tray 4.3.0 does not handle the shell's TaskbarCreated broadcast anywhere, so
    the icon would be gone for the rest of the session while IsRegistered still claimed
    otherwise. MainWindow hooks the broadcast and re-registers; TrayManager.Register
    repopulates the whole NOTIFYICONDATA, icon and tooltip included. Verified by
    broadcasting the message at a running app rather than by restarting Explorer.

Restoring from the tray is Views/WindowRestore: Show() first, then WindowState. The
intuitive order fails silently — WPF defers a state written to a hidden window, so
the property reports Normal and IsVisible reports true while the HWND stays iconic,
which is a taskbar button and no window. Reported from the field during this work and
measured with IsIconic; E2E/WindowRestoreTests pins it against a real Window and
asserts on the HWND, because trusting the managed properties is what let it through.

NotifyIcon reports a registration failure nowhere, and a missing icon looks exactly
like one in the overflow flyout, so MainWindow logs the outcome — the same diagnostic
role the hotkey backend line has. AppLog's "Warn" spelling is normalised to "Warning"
so one grep finds every warning.

Also drops Phase 6 (Supabase + Discord OAuth) from the roadmap: a hosted backend is a
recurring cost for a single-machine companion app, and Discord identity buys nothing
when there is nothing shared to identify against. The %AppData% JSON files stay the
only persistence.

Tests: 389 green.

fix: a failed refresh was erased by the next catalog navigation

Settings said "offline", the user opened the Catalog, and the green cloud came
back over data nothing had re-validated. The load in between made no API call at
all — the 12 h version-check timer had not elapsed — and that path published
CatalogStatus.Online unconditionally.

So Online meant two different things: "the API answered just now" and "we did not
ask". The second one silently overwrote the first, which is the whole defect — a
badge a page change resets tells the user nothing they can act on.

CatalogStatus now reports what the last contact taught us rather than what the
current load did, decided in one place, ContractCatalogService.CachedStatus:

  • the rate-limit window is read live, because it expires on its own;
  • _apiUnreachable is sticky, because unreachability does not expire. Only an
    answer from the server clears it, and a 429 counts as one: the server replied,
    so a stale offline verdict has to go, and the window alone represents the 429.

The flag is in memory only, deliberately. A failed attempt is positive evidence
and must survive navigation while the app runs; a fresh launch holds no such
evidence, and absence of evidence is not evidence of a problem. Answering that at
startup would mean a version check on every launch, which is exactly what the 12 h
timer exists to avoid.

Covered by E2E/CatalogAvailabilityScenarios for the journey (forced refresh fails,
then the catalog is opened) and two ContractCatalogServiceTests for the carry
forward and the 429 interaction. Verified the first two fail with the old
unconditional Online; the 429 one guards the clearing rule going forward rather
than this fix.

Also drops "Start with Windows" from Phase 5: a companion app for one game does not
belong in every boot, and the tray already keeps it out of the way for the rest of
the session.

Tests: 392 green.

add: Favorites tabs, a completion filter, and a gathering grid (Phase 2.5)

The gathering plan sat in an Expander above the contract list, so opening it
pushed the list — the reason the page exists — down to roughly one visible card.
That is why the panel had to ship collapsed: both halves were competing for the
same vertical space and the loser was whichever one you were not looking at.
FavoritesPage is now a TabControl, and each half gets the whole page.

The count badge moves from the Expander header to the gathering tab's header,
which is the job that header was really doing — keeping the number glanceable
from the other tab. Both TabItems are always present and their states live
inside them: binding TabItem.Visibility would hide a selected tab, and WPF does
not move the selection off one that disappears, leaving a blank content area.
The filter bar moves inside the contracts tab, which makes "the plan ignores the
page's filters" structural rather than a rule stated in docs/ui-notes.md.

ContractFilter gains a completion axis so both list pages can hide finished
contracts without un-starring them. Completion is a parameter of Matches rather
than a field read from ICompletionService: it is service state keyed by UUID,
and taking that service would cost the record the purity that lets it be tested
without a WPF Application.

One consequence needed its own care. OnCompletionChanged fanned out to the cards
and stopped there, so a contract completed while "not completed" was selected
stayed on screen until something else re-filtered. It now refreshes the
collection view too, unconditionally — completion changes at click rate. That
refresh is deliberately NOT in OnInventoryChanged, which is the ~30x/s
held-hotkey path.

With a tab to work with, the plan renders as a card grid whose column count
follows the window width (WidthToColumnsConverter over a UniformGrid, minimum
column width as ConverterParameter). Not WrapPanel + a fixed ItemWidth, which
flows at one width and leaves a ragged right gutter. Each card states what is
gathered against what the starred contracts ask for, a coverage bar, and its own
overlay pin — so an item can be pinned from the list where the decision is
actually made instead of finding the name again on the Inventory page.

The card does not spell the shortfall out separately. "12 / 36" already says it,
the tab is titled "What to still gather", and a third number was the one that
wanted a status colour of its own.

GatheringItem gains CoveredFraction, beside Outstanding, because it is the same
derived arithmetic and belongs with the rest of it rather than in a row view
model. SyncGathering reconciles the displayed rows in place against the plan's
name ordering: rebuilt rows would discard every PinToggle to move one number,
thirty times a second while a hotkey is held. GatheringRowViewModel.Update
returns early on record equality for the same reason — the plan is recomputed
whole, but one edit moves one row.

The pin affordance and the "Overlay 3/10" budget are extracted into PinToggle
and OverlayPinsViewModel, shared by the inventory row and the gathering card.
The budget VM is a singleton: there is one set of pins, and a second counter
would only be a second thing to keep in step.

Availability colour stops going through an IValueConverter, and this fixes a
real defect rather than tidying one. A converter runs when its binding evaluates
and returns an already-resolved Brush; nothing re-runs it when a resource
dictionary is swapped, because the binding's source has not changed. So after a
runtime light/dark flip every converter-supplied colour stayed on the old
palette while DynamicResource chrome moved with it. Chips.xaml now does it with
Style.Triggers + DynamicResource in AvailabilityChipStyle and the new
AvailabilityValueStyle, and AvailabilityToBrushConverter is deleted. Chips had
hidden the fault because fill, border and text went stale together and stayed
internally legible; the pin badge next to a stale number is what exposed it.
Verified by rendering the page, flipping the theme at runtime, and re-shooting —
a cold start proves nothing here, since everything resolves against the palette
already merged.

WPF-UI themes stock TabControl, but three of its tokens are unusable over a Mica
page: the selected header's background is SolidBackgroundFillColorTertiary
(#282828 on a #202020 page) and both border keys are 10% black. They are
re-pointed inside TabControl.Resources, where the templates' DynamicResource
lookups find them and nothing else does, using Colors + DynamicResource rather
than a StaticResource alias to a brush key, which would freeze one theme's value
into the other. The rule under the tab strip is the content Border's top edge, so
BorderBrush on the TabControl is all it takes.

Tests: 426 green.

0.9.6

Choose a tag to compare

@github-actions github-actions released this 03 Aug 12:34
09f171a

What's Changed

fix: global hotkeys never reached the app in game, and elevation was never the cause

RegisterHotKey delivers through the system hotkey table, and a foreground
application can take that table out of service for everyone (Raw Input's own
RIDEV_NOHOTKEYS does exactly that). The symptom was the worst kind: registration
succeeded, nothing reported an error anywhere, the keys worked on the desktop,
and nothing arrived once Star Citizen had focus.

It was first misdiagnosed as UIPI, and an elevation workaround was built for it.
Running as administrator does not fix it — verified in the field — so AppElevation
and the Settings "Restart as administrator" row are removed rather than kept.

Delivery is now a strategy behind IHotkeyBackend:

  • RawInputBackend (default) subscribes a keyboard usage page with RIDEV_INPUTSINK,
    so the system posts WM_INPUT straight to the sink whoever is in front.
  • RegisterHotkeyBackend (fallback) is the old path, kept because it is the only one
    that claims a combination exclusively.
  • OverlaySettings.HotkeyBackend forces one; Auto prefers Raw Input and falls back.
    The live backend is named in the log at startup.

Two consequences, both user-visible. Raw Input does not claim the combination, so
the game still receives the same keystroke — the Settings hint said the opposite
and is corrected in both languages. And the sink sees every keystroke on the
machine, which is why HotkeyLookup.IsTrigger runs first: unrelated input is dropped
in one set lookup, before anything is read, stored or logged.

The sink window is no longer message-only. WM_HOTKEY would reach one, but a Raw
Input sink registered against a message-only window never receives WM_INPUT.

Still no injection, no SetWindowsHookEx, no reading SC memory. Raw Input is passive:
it cannot swallow, alter or inject a keystroke, which is what separates it from a
hook.

Phase 4 is closed — in-game verification confirmed by the user, unelevated.
Tests: 378 green.

fix: sync scenarios waited on the flag instead of the cards they then asserted on

All three CatalogSyncScenarios released the held enrichment and then waited on
!IsSyncing before reading the rebuilt card list. Those are two different events.

ContractCatalogService raises SyncStateChanged(Idle) before CatalogUpdated, and
deliberately so — a CatalogUpdated subscriber must never observe the catalog as
still syncing. Between those two dispatcher hops the page is idle while still
holding the pre-enrichment cards, whose category is Unknown, so the category
filter matches none of them and Assert.Single sees an empty list.

Caught as one intermittent failure of the category-filter scenario during a full
run right after a build. Both waits now go through WaitForRebuildAsync, which
listens for CatalogUpdated: the view model subscribed to it first and rebuilds
inside a blocking Dispatcher.Invoke, so by the time the test's handler runs the
new cards are already in place.

Verified with five consecutive full Release runs, 378 green each.

0.9.5

Choose a tag to compare

@github-actions github-actions released this 02 Aug 21:43
a5cf52f

What's Changed

add: file log next to Update.exe, with an Open logs folder button

  • The app now keeps a readable log, and About has an Open logs folder button that lands you on
    it. When an update misbehaves there is finally something to look at instead of guesswork.
  • Crashes are recorded too — the unhandled-exception handler was an empty TODO.

Where the file goes, and why it matters

Services/AppLog writes WikeloContractor.log into the install root, beside Update.exe — not
beside the executable. The exe lives in current\, which Velopack replaces wholesale on every update
("Backing up current dir… Replacing current dir with…"), so a log written there is destroyed at the
one moment it is needed: right after an update went wrong. A dev run has nothing above it, so it
falls back to the binaries' folder.

AppLog is static and dependency-free on purpose: VelopackApp.Build().Run() is the first line of
startup and the update hooks it handles run and exit before the host is ever built.

What ends up in it

  • FileLoggerProvider routes the host's ILogger output there, from Information up — Debug/Trace
    from the host and HttpClient would bury the app's own lines, and this file is meant to be read by
    a person.
  • VelopackFileLogger adds Velopack's managed half (locator decisions, "app is out-dated").
  • MirrorUpdaterLog copies Update.exe's own log out of the hidden %LocalAppData%\velopack\.
    That half cannot be redirected — ApplyUpdatesAndRestart builds its command line without --log
    and it is the half that carries the apply steps, so one findable folder now holds the whole story.
    Skipped in a dev run, where there is no updater history and the file is ~130 KB per launch.

Logging never throws: a read-only directory or a locked file loses a line rather than breaking the
app. Rotates at 1 MB keeping one previous file.

Services/AppVersion is extracted so the log banner and the About page resolve the version once
instead of twice.

fix: portable update left two launchers and stale docs claimed it never updates

  • Updating a portable install used to leave two executables side by side —
    Wikelo Contractor.exe from the download and WikeloContractor.exe created by the updater. A
    shortcut to the first one kept starting the old, stale launcher after every update.

vpk pack names the portable launcher after --packTitle, while Update.exe re-creates it from
--mainExe, so a title with a space could never match. Confirmed from the updater's own log:

Extracting stub 'WikeloContractor_ExecutionStub.exe' to '...\WikeloContractor.exe'

and reproduced end to end by applying 0.9.4 onto a clean 0.9.1 in a scratch directory. This is
upstream bug velopack/velopack#982, filed against the pinned 1.2.0 and fixed in PR #985 — but
released only in the 1.2.110-* prerelease, so --packTitle is set equal to the --mainExe base name
instead, which is the fix that works on stable. The constraint is written next to the value, since
the obvious "improvement" is to restore the prettier spaced title.

Existing installs keep the orphaned Wikelo Contractor.exe until it is deleted by hand; the updater
does not remove it.

Documentation was wrong about portable updates

CLAUDE.md, PLAN.md and a release.yml comment all claimed portable builds do not auto-update because
IsInstalled is false. They do: Velopack treats the .portable + current\ + Update.exe layout as
installed, and Check for updates really applies a new release there — verified in the field and in
a scratch reproduction. IsInstalled is false only in a dev run. That mistake mattered: it made the
launcher bug look cosmetic when it actually bites every user who updates, and it propped up the
"auto-update is moot while unsigned" line in the signing rationale, which is also removed.

add: in-game overlay with global hotkeys (Phase 4)

A small always-on-top HUD listing up to ten pinned inventory items, so counts
stay accurate during a session without alt-tabbing out of Star Citizen.

  • Pin items from the Inventory page (per-row button, Overlay N/10 counter, a
    reset-all button); PinnedItemsService is the fourth JSON store, an ordered
    name list where the order is the slot.
  • Two configurable modifier patterns plus a digit drive +1/-1 per slot, rather
    than twenty separate bindings; plus show/hide and interactive-mode toggles,
    captured in Settings through the new HotkeyBox control.
  • Interop/NativeMethods is the repo's first and only P/Invoke surface;
    HotkeyService owns Win32 on a message-only HwndSource and reports partial
    registration failure instead of rolling it back. OverlayService decides what a
    press means and is the only piece that knows a window exists, which is what
    makes the whole overlay testable through IHotkeyService.Pressed.
  • InventoryStore hardened first: hotkey auto-repeat (~30 edits/s) against the
    old unlocked, fixed-temp-path, fire-and-forget write would have thrown
    IOException into a swallowed task and lost writes. Now a debounced write
    behind a semaphore, with Flush()/FlushAsync() and AppLog on failure.
  • Overlay corners are square and its height follows content: on a transparent
    window a rounded corner anti-aliases against the backdrop, and a restored
    fixed height silently clipped the tenth row.
  • Hotkeys do not reach an unelevated app while an elevated game is foreground
    (UIPI) — AppElevation detects it and Settings offers Restart as administrator.
    No injection, no keyboard hook, no reading game memory.

344 tests green.

fix: sourcing guide reload test was a coin flip on write-time granularity

Editing_a_fragment_refreshes_every_guide_including_it writes the fragment
twice back to back. SourcingGuideService fingerprints files by path plus
LastWriteTimeUtc, and Windows advances that on the ~15.6 ms clock tick, so both
writes usually land in the same tick and the service correctly concludes
nothing changed. Measured on the dev machine: 151 of 200 back-to-back writes
get an identical timestamp. Debug passed only because parsing and asserting
between the writes straddled a tick often enough; a Release CI run did not.

Forces a distinct timestamp after the second write, the same idiom
CatalogImageOverrideServiceTests already uses for this exact hazard. The test
is about the reload signature covering the _shared folders, so removing the
filesystem race does not weaken it.

0.9.4

Choose a tag to compare

@github-actions github-actions released this 02 Aug 10:44
b17e8f7

What's Changed

add: sourcing guides for every required item, with shared fragments and contract metadata

  • Where to Find now has a written guide for all 95 required items — it had one. Each page
    explains the activity, the steps, and links the community walkthrough it came from.
  • Guide sections fold away: every ## heading is a collapsible expander, open by default, so a
    long step list can be collapsed while reading.
  • Guides can show pictures![alt](url) renders a map slide or screenshot, disk-cached like the
    reward thumbs, so it works offline after the first load.
  • Each page shows which contract yields the item and who hands it out, when that is known.
    Bought or mined items show neither row instead of blank labels.

Markdown renderer

  • MarkdownDocument gains block-level ![alt](url) images and {{include: key}} fragment splicing.
    Both are total like the rest of the parser: malformed input stays plain text. An image between two
    numbered steps deliberately does not restart the numbering.
  • MarkdownViewer folds each ## section into a stock themed Expander (WPF-UI ships
    DefaultExpanderStyle, so no hand-rolled disclosure control) and resolves images through
    ThumbnailLoader — the same disk-cached path the reward and inventory thumbs use.
  • Fixes a latent crash: Font() returns null when the resource is missing, and WPF throws on a null
    FontFamily, so an unresolved MonoFontFamily took the whole guide page down.

Shared fragments

  • Text several guides share lives once in docs/sourcing/_shared/ and is pulled in with a
    {{include: key}} line. 14 fragments now back 95 guides; the Onyx mechanic sits in one file rather
    than nine, and the two source links in one rather than twenty-three.
  • Fragments are keyed by front matter name, layered bundled/%AppData% like the guides, and can
    never become guides because the guide scan is top-directory only. Expansion is one level deep, so
    fragments cannot reference each other into a loop.
  • The reload signature covers the fragment folders, so editing a shared block refreshes every guide
    that includes it.

Guide metadata

  • Optional contract: and faction: front matter render as labelled rows at the top of
    How to obtain, each hidden on its own. contract holds the candidate mission name; where two
    names are in circulation both are given rather than one being picked.
  • Data tests reject unknown front matter keys and blank optional values — a typo would otherwise
    render nothing, silently, on one page.

Authoring workflow

  • New sourcing-guide skill captures how these are written: validate the name against the catalog,
    fan out to every sibling item one source feeds, ask only about genuine ambiguities, and never
    invent a game fact. It also records why the API, the wiki, Spectrum and the community trackers
    cannot supply this, so the research is not repeated.
  • Two rules worth keeping: never name a shop or quote a price (both are live data that go stale
    immediately — defer to the shop finder and SCMDB), and keep every list item on one physical line,
    since the parser trims lines and a wrapped item silently resets ordered numbering.

Fixes a stale user-facing string that told users to correct notes in sourcing-notes.json, a file
that no longer exists.

update: rename the sidebar's Catalog item to Contracts

  • The first sidebar item now reads Contracts instead of Catalog, matching the heading of the
    page it opens ("Wikelo Contracts") — clicking "Catalog" and landing on "Contracts" was a small but
    needless stumble.

Naming, not behaviour: Nav_Catalog is referenced only by the sidebar item in MainWindow.xaml, so
this is one string per language. CatalogPage / CatalogViewModel / the resource key keep their
names — internally "catalog" is still accurate, it is the cached data set, and renaming them would be
churn with no reader benefit.

Every other sidebar label already agreed with its page: Favorites, Where to Find and Settings match
word for word, and AboutPage reuses Nav_About as its own heading so the two cannot drift. Catalog
was the only real mismatch. Inventory / "My Inventory" is left alone — a possessive page heading over
a short nav label is the same pattern this change lands on.

fix: inventory quantity field showed only a single digit

  • The quantity box on the Inventory page cut the number off after one digit, so a stock of 10000 was
    unreadable and unverifiable while typing. It now shows five digits comfortably, with room for six.

The box was sized 110px, which looked generous but is not how a NumberBox divides its width: the
inline spin buttons take their desired size first and the text area receives only the remainder.
Buttons plus padding came to roughly 95px, leaving ~14px for the text — exactly one digit. Widened to
150px, sized from the real requirement (scrip is traded in the tens of thousands, so five digits is
the floor) rather than from how the control looks.

The reasoning is recorded in a comment next to the value, because the number reads like arbitrary
padding and the obvious "tidy-up" is to shrink it back.

0.9.3

Choose a tag to compare

@github-actions github-actions released this 02 Aug 10:32
48be141

What's Changed

build: re-stamp portable zip timestamps with the release commit date

  • Files extracted from WikeloContractor-win-Portable.zip now show a real modified date in
    Explorer instead of 31.12.1979 23:00, which looked like a corrupted or tampered download.
  • No change to how the app installs, runs, or updates — the portable zip is repacked with the
    same contents, only the file timestamps differ.

0.9.1

Choose a tag to compare

@github-actions github-actions released this 27 Jul 23:37
48be141

What's Changed

build: re-stamp portable zip timestamps with the release commit date

  • Files extracted from WikeloContractor-win-Portable.zip now show a real modified date in
    Explorer instead of 31.12.1979 23:00, which looked like a corrupted or tampered download.
  • No change to how the app installs, runs, or updates — the portable zip is repacked with the
    same contents, only the file timestamps differ.

0.9.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 15:38
d72bd89

What's Changed

add: bundled item images resolved from install-dir-relative override paths

  • Items that no wiki or community CDN has a picture for now show a real image instead of the
    category-icon placeholder: all 95 required items are covered, and 65 of the 69 rewards
    without an API image (the 4 that remain are listed in docs/reward-images.md).
  • 27 hand-made screenshots and crops ship with the app — visible both as the 46×46 thumb and in
    the full-window preview overlay, which means practically every inventory row is now clickable.
  • The image override files accept a new, simpler value: a path relative to the install dir. You no
    longer need a hosted URL or a machine-specific absolute path to add your own picture.
  • Personal images still belong in %AppData% — the bundled folder is replaced on every update.
    src/Resources/img/README.md is the step-by-step for adding one.
  • The "Where to Find" navigation item gets a location icon instead of the book/question-mark glyph.

0.8.5

Choose a tag to compare

@github-actions github-actions released this 24 Jul 11:20
98fdc71

What's Changed

add: favorites page and Markdown-backed sourcing guide for required items

build: publish portable-only releases with provenance and checksums until code-signed

  • Releases now offer a single download — WikeloContractor-win-Portable.zip; the unsigned
    Setup.exe/.msi installers are no longer published.
  • Every release carries a SHA256SUMS.txt and a GitHub build-provenance attestation, so a
    download can be verified before the app is code-signed.
  • README explains why it's portable-only for now and how to verify a download; it no longer
    suggests weakening Windows security (AV/ASR exclusions, Smart App Control) to run the app.
  • Added a SECURITY.md with how to report vulnerabilities and confirm a genuine build.

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 22 Jul 22:35
00cd0fa

What's Changed

add: adaptive vector app mark, inventory image overrides, and commit-based release notes

Branding & icon

  • Title-bar icon is a DrawingImage vector mark (Resources/BrandIcons.xaml, AppMarkDark/AppMarkLight) resolved from app resources; the raster taskbar Window.Icon is chosen from SystemUsesLightTheme (shell theme) rather than the app theme, because the two Windows 11 theme settings are independent and mismatching them dropped contrast to ~1.2:1.
  • UpdateAppIcon gates on a cached (appTheme, shellLight) pair and caches two frozen BitmapImages, so the frequent SystemEvents.UserPreferenceChanged/General events no longer re-read the registry or re-decode a PNG unless a relevant signal changed.
  • About hero switched to ui:Image with CornerRadius, dropping a hand-maintained Clip rect and letting the image stretch to the column width. New raster assets (icon.png, icon-light.png, app.ico, about-hero.png, banner) plus brand reference docs (docs/brand/*).

Override configs

  • Renamed image-overrides.jsonimg-catalog-overrides.json and inventory-image-overrides.jsonimg-inventory-overrides.json for a consistent img-* scheme (csproj <Content>, services, docs updated).
  • OverrideFileSet gained one-time adoption of a pre-rename %AppData% user file: callers pass only a legacy file name and the engine composes it against the user file's own directory, so the "legacy sits beside the user file" rule lives in one place and any future override config inherits the migration.
  • Bundled img-inventory-overrides.json seeded with all 95 required-item names, 56 filled from starcitizen.tools; new docs/inventory-images.md mirrors reward-images.md.

Release CI

  • Replaced GitHub's generate-notes (which lists merged PRs, collapsing multi-commit PRs to one line) with a git log --no-merges --reverse generator that renders each commit subject as a heading and its full body underneath, stripping co-author/tool trailers; dropped the redundant "Full Changelog" link. Release PR template no longer claims its bullets feed the notes.

Docs

  • Image-inventory docs use ✅ / ⬜ glyphs (which render inside Markdown table cells) instead of [x]/[ ] task-list syntax (which does not).

add: blueprint grants on contracts and editable inventory quantities with image preview

  • Contracts that grant a crafting blueprint (e.g. "Metamaterial Test #152") now show it: a dedicated Blueprints section on the detail page and a compact "BP:" chip on the catalog card.
  • Inventory quantities are now typed directly into an editable box (enter 100 / 1000 in one go, or step by one) instead of clicking a +/- counter.
  • Inventory item images open in the same full-window preview already used for contract rewards — click the thumbnail, click anywhere or press Esc to close.
  • Removed the per-contract game-version badge from the detail page (the version is already shown on the catalog).

add: visible gating catalog sync state, synthetic E2E tests, and Fluent design-system token layer

  • Catalog now shows a clear "syncing" state — an animated overlay with a progress bar — and the green "synced" cloud badge no longer claims to be synced while data is still loading.
  • The whole app is locked during a sync (navigation, inventory edits, theme/language switches, the About "check for updates" button) so you can't act on half-loaded data.
  • Contracts can no longer be marked complete mid-sync, which previously deducted the wrong amounts from your inventory.
  • Sync wording corrected: it says API data is syncing, not that the game version is updating (a separate updater handles that).
  • Rate-limited catalog loads now automatically resume when the wait window elapses (capped and polite) instead of sitting on "resuming…" forever.
  • Light theme is more legible — higher-contrast text plus borders on chips and badges.
  • Restored the brand accent colour that the system theme watcher had been overwriting, and the readiness progress bar's correct rounded, themed look.

fix: announce catalog sync completion after releasing the enrichment run guard

  • A catalog refresh triggered right after a previous sync finished could silently fail to start — leaving the catalog on stale, un-enriched data (wrong category filters) until another refresh happened. It now always starts.