Repository navigation
feat(ui): all-time range, filter fixes, currency switch, model-share card, artwork - #4
Merged
Merged
Conversation
Ticking one model collapsed the candidate list to that single entry: the query loop registered a model in `models` only after the selection filter, so the response advertised just the selected key. Register the candidate first, keep the aggregate filter where it was, and ship the composite `key` on each entry so the client stops re-deriving `provider/model`. That second half also fixes a silent blank report: with an empty provider the client built `/model`, which matched nothing, so total/daily/byModel all came back 0. The filter popovers were `position: absolute` inside the settings panel, which is `overflow: hidden`, so their right half was clipped. They now sit in a measured `position: fixed` panel that flips up when the space below is too tight, caps its height, and re-measures on scroll/resize. The model list declares `min-height: 0` so it shrinks instead of being cut, and the popover declares border-box so `width: 320px` really is 320px.
The custom-range popover said "date only, up to 30 days", but nothing enforced it: the host only checks that the bounds are numbers, the date inputs carry no min/max, and the one real limit is MAX_BUCKETS (2000 output buckets, only reached while zero-filling). The hint was the only thing that looked like a limit. Add an "all" preset that starts at 0 and asks for present-only buckets (fill: false), so it never zero-fills 1970..today and never trips MAX_BUCKETS. Custom ranges keep the existing day-step collapse, which governs chart density rather than correctness; the hint now says so plainly.
`currency` was a bare display symbol with no conversion behind it, while the default table is DeepSeek's USD list price per 1M tokens — printed with a ¥ sign, so the visible CNY number sat about 7x below the real CNY list (0.27 / 1.10 / 0.07 USD is 2 / 8 / 0.5 CNY at 7:1). The table is now documented as USD and never rewritten: `query` accepts a currency, converts once at the output boundary (daily, byModel, total and heatmap, a single rounding at 1e-6 so the sums stay exact), and returns the currency it actually used. CNY/USD resolve through a fixed 7:1 table; unknown or empty values fall back to the configured currency instead of throwing. Client side, the reference-cost card carries a ¥/$ picker in its value row. The choice is remembered locally (a display preference, not plugin config) and triggers a re-query, because conversion happens host-side. Amounts below half a cent render "<0.01" instead of a misleading 0.00.
The refresh button was a 34px bordered tile wrapping a 24-grid, stroke-2
glyph, which read as a foreign control inside a settings panel. It is now a
28px transparent icon button (hover fills, no border; the shared
:focus-visible group already covers it) using the DSH native refresh glyph on
its 16-grid at stroke 1.5, and it disables itself with aria-busy while a
refresh is in flight.
`SvgIcon` now accepts an icon that carries its own viewBox, stroke width and
path list, so both grid systems coexist; the string-based call sites are
untouched.
"成本" now reads "参考成本" ("Est. cost" in English) on the card and in the
model table header, which is what the number actually is: a price-table
estimate, not a billed amount.
Token share per model lived only in the summary table's ratio column, so at a glance there was no sense of which model dominates the selected range. Add it as a sixth cell of the same `.tsd-cards` grid instead of a new container: the grid is `repeat(auto-fit, minmax(150px, 1fr))` and only direct children take a cell. The cell reuses the host `byModel` rows the summary table already renders, so the two views cannot drift — top four models by total tokens plus an aggregated "Others" segment; hover/focus switches the caption, and each segment's `title` carries input/output/cost with the same unpriced rule as the table (tokens > 0 and no cost). Deliberately no floating tooltip: this card sits against the settings panel's overflow boundary, so the detail rides on the native title rather than re-introducing the clipping the popovers just got rid of.
Two places, one shape. `icon.svg` (three bars, vertical gradient) is declared in `package.json#icon` and shipped through `files[]`, so the plugin manager can render it on the plugin card; the settings nav row uses the same motif. The nav icon has no contract to go through: `settings.section` projects only id/order/label, and the settings shell resolves the glyph from a hardcoded id whitelist, so a third-party section always falls back to the gear. Registering under a first-party id would take over a built-in row instead, so this marks its own row by visible label and swaps the gear for a masked glyph — the same technique the other third-party plugins in this profile use. It never replaces the svg node (React throws on removeChild during unmount), degrades to the gear when the row or the DOM is missing, and is disposed with the fiber. The mask reads only the alpha channel, hence the flat single-colour glyph. Commented for deletion once the shell grows an icon field on the section contract.
Document the six dashboard changes and bump to a feature release. The default price table stays DeepSeek's USD list; it is now converted for display instead of being printed with a ¥ sign, which is why the visible CNY figures grow about 7x. Called out in the changelog so it does not later read as a regression.
The previous version put the switcher inside the value row, and `formatCost` already prefixes the symbol, so the card rendered the symbol twice (`¥ ▾ ¥14.78`) while the 15px symbol + 11px chevron broke the numeric baseline. Split the two concerns: `formatCost` stays the only place that draws a symbol, and the switcher becomes a card-header action. - `.tsd-card-actions` (margin-left:auto) holds card-level controls; the trigger is a 20px transparent button with a hover fill, matching the refresh button in `StatsHeader`. - The trigger shows the currency *code* (`CNY`/`USD`) rather than the symbol: the symbol is already on the number, and `$` alone is ambiguous. Arrow keys change the value while closed, move the selection while open, Esc closes. - The menu reuses `.tsd-popover` + `useFixedPanel`, shows symbol + name + a check on the active row, and states the rate in `.tsd-popover-note` (`1 USD = 7 CNY`).
The card only had a head, a thick bar and one caption line, so it read as a chart wedged into the KPI grid instead of a KPI. Its detail text also went through a native `title`, whose black box covered the neighbouring card. - Layout now mirrors the other cards: head / value (largest segment's percentage, same 20px/700 as `21,536`) / sub (that segment's short model name, 12px ellipsis) / a 4px bar under both. - Hovering or focusing a segment swaps value and sub together, then restores the largest segment on leave. - Detail moved from `title` to the shared `.tsd-tooltip` drawn at fixed coordinates from `getBoundingClientRect` (the heatmap already does this), so the settings panel's `overflow` cannot clip it. - The "others" segment is a tinted brand colour (`color-mix`) rather than grey, which reads as disabled.
The plugin page showed the bare package name and the long English
`description`, because `readPluginMeta` in `dsh-app-boot` falls back to
`package.json#name` / `#description` when the package ships no
`locale/<lang>.json`.
Ship the dictionaries the loader already looks for:
- `locale/en.json` and `locale/zh.json` with `{"meta":{title,
description}}`, the shape `dictionariesOf` parses.
- `"./locale/*.json"` in `exports` and `locale` in `files`, since the
loader resolves `<pkg>/locale/en.json` through the package resolver
and the tarball must carry it.
- Redraw `icon.svg` in the shipped art's idiom: transparent 36x36,
opaque shapes on a diagonal `#67C7FE → #3F77D8` gradient. The old
art stacked three 50/75/100% opacity bars, which read as washed out
next to the official icons.
…re card Side-by-side, the row-style versions read better, so both go back to the shape the fork ships while keeping the fixes that came out of the remake. Currency switch: - Trigger returns to the value row and draws the symbol itself, so the symbol appears exactly once (`formatCost` no longer supplies one) and the control inherits the 20px/700 number's baseline instead of fighting it. - Keeps the keyboard handling (arrows/Esc/Enter) and the menu note that states the fixed 1 USD = 7 CNY rate. Model share: - Back to a 10px pill bar with an 11px tertiary caption underneath (name + percentage), which keeps the KPI numbers as the row's headlines. - Keeps the fixed-position tooltip: the native `title` it replaced was what painted a black box over the neighbouring card. - Drops the icon's invented pie path for the shipped two-stroke glyph, and drops the now-unused `shareHint` string.
…ours Three defects from the first work-exp pass: - The cost card printed `¥ ¥14.78`: the switcher draws the symbol and `formatCost` still prefixed it. Split out `costDigits` so the card renders digits only and the symbol has exactly one owner. - The rate note ran out of the popover: the switcher lives inside `.tsd-card-value`, which sets `white-space: nowrap` for the number, and the popover inherited it. Reset to `normal` on the popover. - Two share segments rendered as grey and white: `--color-blue-500` and `--color-orange-500` are not DSH tokens, so those backgrounds fell back to nothing. Use only families the theme actually ships (deepseek / green / amber / red) and tint "others" with `--dsw-static-deepseek-300` instead of a neutral grey, which reads as disabled on a dark card.
Collaborator
Author
|
@ruanbw 一个发布上的缺口想确认下: README 写的两种装法(
|
Contributor
|
@xinspark 已经发布到 npm 官方源(v0.3.0),并将 npm 页面地址(https://www.npmjs.com/package/dsh-plugin-usage)加到了仓库的 Website/URL 中。感谢提醒! |
Collaborator
Author
|
@ruanbw 谢谢,确认过了:npm 上 |
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.
Six panel improvements, plus three defects found while exercising them on a live profile. Everything stays inside the plugin's existing shape: one client file, CSS in the same template string, both dictionaries, labelled
ctx.effect()calls, no new dependencies.What changed
1. All-time range, no artificial span limit
rangeFor('all')maps to{from: 0, to: now}withfill: false, so the all-time view only buckets days that carry data and never approachesMAX_BUCKETS. The custom range no longer advertises a "30 days max" limit: the only real bound is the bucket guard, and the hint now says so.fillstays on for the rolling presets, where empty days are meaningful.2. Model filter: candidates no longer collapse, and survive records without a provider
Two separate defects:
seenModelsafter applying the model filter, so the response only offered the models already selected; picking one hid the rest.provider + '/' + modelunconditionally, while the host key is the baremodelwhen a record has no provider. Selecting such a candidate matched nothing, and the panel silently showed zero.The response now carries the host's composed
keyfor every model and the client uses it verbatim, so both sides share one definition.3. Popovers are no longer clipped by the settings panel
.tsd-popoverwas absolutely positioned inside a panel withoverflow: hidden. A smalluseFixedPanel(anchorRef, open, width)hook measures the anchor, positions the panelfixed, flips it upward when there is no room below, and capsmaxHeight. The model filter, the custom date range and the new currency menu all use it.4. Currency switch for the reference cost (CNY / USD, fixed 1 USD = 7 CNY)
DEFAULT_PRICESholds DeepSeek's USD list (0.27 / 1.10 / 0.07) but was printed with a¥. The table is now explicitly USD-based and CNY display multiplies by 7, which lands on DeepSeek's published CNY list — so this also corrects the previous numbers, which were 7× too low.query(costOf,daily,byModel,total,heatmap); the price table itself is never rewritten, andhasPricestill decides priced vs unpriced.costDigitsreturns digits only, since two owners produced¥ ¥14.78. The menu states the fixed rate.localStorageas a display preference; it never touches the plugin configuration.5. Model-share card (6th KPI)
A 10 px pill bar with an 11 px caption (
deepseek-flash 53.5%), as a normal.tsd-cardsitem. Colours come from families the theme actually ships (--dsw-static-{deepseek,green,amber,red}-*); "others" is tinteddeepseek-300rather than neutral grey, which reads as disabled on a dark card. Per-segment detail is afixedtooltip (the heatmap's technique) instead of a nativetitle, whose black box covered the adjacent card.6. Artwork and plugin-page metadata
package.json#iconplusicon.svg(transparent 36×36, diagonal#67C7FE → #3F77D8).locale/{en,zh}.jsoncarryingmeta.title/meta.descriptionand the matching./locale/*.jsonexport: without them the plugin page falls back to the bare package name and the truncated English description.settings.sectioncannot carry an icon: the runtime'snavIcon(id)is a closed list and returns a gear for any other id. The patch only adds adata-tsd-navattribute and paints the glyph with::before+mask; the React-owned<svg>is never replaced (doing so makes React'sremoveChildthrow when the panel unmounts). If the row cannot be identified, nothing happens. This block should be deleted oncesettings.sectionaccepts anicon.Verification
npm run build,node --check lib/client/index.js,npm test— 83 pass, 0 fail (6 new host tests for the filter and currency paths).