Skip to content

0.9.8

Latest

Choose a tag to compare

@github-actions github-actions released this 26 Aug 23:37
· 0 commits to dev since this release
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 substitution and adding one to MarkdownDocument for this would
be a poor trade; the per-item link belongs to the page, not the text.

One duplicate removed rather than created: the three keys ui:HyperlinkButton
hard-codes its text colour through were a local block on the contract detail
page, where the pledge link two hundred lines below had quietly kept the stock
colour. They are in Chips.xaml now, so every external link matches. A Style
cannot do this — FrameworkElement.Resources is not a dependency property.

fix: the cstone button dead-ended on every ore, and Tungsten had no uuid at all

Two faults with one root: I did not check what the finder actually holds.

The Universal Item Finder is a database of items, and it does not carry
mineable materials. /Search/{uuid} for one quietly redirects to its home page,
so the button landed the player on an empty search box — the same broken promise
as the "Where it is sold" label removed in the previous commit, only expressed
as a button that should not have been there.

Measured over all 94 recorded UUIDs rather than guessed, because the boundary was
worth knowing exactly: every one of the ten OreMineral items redirects, and every
item in every other category resolves but a single consumable. So ShopFinder now
returns nothing for InventoryCategory.OreMineral. The gate is the category the
item already carries — no per-guide flag to record and nothing to go stale as the
finder's crowdsourced data grows. The wiki half stays: the wiki does have ores.

Expired Quantanium Fuel Canister is the one non-ore that bounces, and it is left
alone deliberately. A name-shaped exception for a single item is the kind of rule
that rots, and with a label that promises only a destination, one dead end in
eighty-four is a smaller cost than the exception.

Tungsten's name was right all along; the seeding pass was wrong. filter[name]
matches on SUBSTRING, and with page[size]=1 the search returned "Stirling
Exploration Backpack Tungsten Edition". The exact-name guard rejected that
instead of attaching another item's page to the guide — which is the reason only
four guides came back empty rather than an unknown number coming back wrong — but
nothing looked past the first row. Tungsten has two records, differing only in
container size; the canonical one (slug "tungsten", with a description) is now in
its front matter, so all 95 guides carry a UUID.

docs/sourcing/README.md gains the substring caveat, since the next person to add
a guide will hit it. Its worked example also carried Killshot Rifle's real UUID
under the name "Carinite" — corrected before someone copied it.