| status | in-force |
|---|---|
| summary | Optional rank-by-usage for FNS_CommandPalette and the TDXLU quick-launch overlay: a shared command-usage.json of per-consumer decayed scores keyed by tool#id, the pinned bonus formula with test vectors, and the write rule that tolerates several TDs. |
| since | 2026-09-23 |
| skill | fns-command-registration |
Owner's request, 2026-09-23: frequently used commands should rank higher in the command palettes, as an option. Two consumers run the same registry commands (FNS_CommandPalette inside TD, the TDXLU quick-launch overlay), so usage is shared between them the same way curation is (CommandCuration.md). Drafted by the palette side and reviewed by the launcher's agent the same day; their three requested changes (decay model, several TD writers, pinned formula) are folded in below.
<machine-default user palette>/FNSTools/config/command-usage.json, beside
command-curation.json, resolved exactly as that file is (always the
machine-default user palette folder, never a relocated store).
It is NOT a section of the curation file. Curation rule 7 ("nothing else rides along") stays in force, and a counter written on every run would churn the favourites file and fight its lost-update loop for data that is only a heuristic.
{
"schema": 1,
"consumers": {
"fnstools": {
"Autosave#save-now": {"score": 3.0, "last": 1788352000.0, "count": 5},
"TD_Session#toggle-realtime": {"score": 1.0, "last": 1788351000.0, "count": 1}
},
"tdxlu": {
"Autosave#save-now": {"score": 2.0, "last": 1788350000.0, "count": 2}
}
}
}schema: same rule as curation. A reader that meets a higher number than it knows reads nothing from the file and gives no usage bonus. A writer that meets one records nothing and leaves the file untouched; otherwise its next run would write a schema-1 document over the newer file and wipe it.consumers.<name>:fnstoolsortdxlu. A consumer writes only its own block and reads every block.- Key: the curation identity
tool#id. A preset counts against its target'stool#id, with any@instancepin dropped. Built-in TD commands are ordinary registry tools (TD_Session,TD_Dialogs, ...), so their identities are namespaced like any other package and cannot collide with anothertool#idwithout the registry itself colliding. score: the decayed usage value as oflast(float).last: epoch seconds of the latest recorded run (time.time()).count: raw run total. Display only; never used for ranking.
Only registry commands and presets write. A successful run records; nothing else does:
- FNSTools: after
Run()returns without refusing. Not on arming a destructive command, not on a refused or cancelled parameter walk, not on placing a palette component, navigating (/), or calling a promoted method (~). - TDXLU: in
runToolCommandafter a reply withok !== false. Session verbs, app actions, projects and.toxplacement never touch the file.
Recording always runs, whatever the rank toggle says, so turning ranking on later finds history already there.
Constants:
| Name | Value |
|---|---|
HALF_LIFE |
1209600 seconds (14 days) |
V_SAT |
20.0 |
BONUS_MAX |
8.0 |
PRUNE_BELOW |
0.01 |
All arithmetic is IEEE double. Nothing is rounded except where stated.
On a run (at time now, for key k in the writer's own block):
decay(dt) = 0.5 ** (max(0, dt) / HALF_LIFE)
score = score * decay(now - last) + 1 # absent entry: score = 1
last = now
count = count + 1 # absent entry: count = 1
Storing the decayed score, not the count, is what makes old heavy use fade: 100 runs a year ago contribute about 1.4e-06 today, not 100.
At read (for ranking):
v(k) = sum over every consumer block c holding k of
c[k].score * decay(now - c[k].last)
bonus = min(BONUS_MAX, BONUS_MAX * ln(1 + v) / ln(1 + V_SAT))
bonus is a float in [0, 8]. It is used unrounded.
Pruning, on every write: drop from the writer's own block any entry whose
score * decay(now - last) is below PRUNE_BELOW. A single run falls under
it after about 93 days of no use.
| Case | Input | Expected |
|---|---|---|
| Never used | v = 0 |
bonus = 0.0 |
| One run, just now | v = 1 |
bonus = 1.8214 (4 dp) |
| One run, 14 days ago | score 1, dt = 1209600 so v = 0.5 |
bonus = 1.0654 |
| Update after one half-life | score 4, dt = 1209600, then a run |
score = 3.0 |
| Summed across consumers | fnstools v = 3, tdxlu v = 2, so v = 5 |
bonus = 4.7082 |
| Saturated | v = 20 or v = 100 |
bonus = 8.0 |
| Old heavy use | score 101, dt = 365 days |
v ~ 1.43e-06, bonus = 0.0000 |
Compare to 4 decimal places; both sides mirror these as unit tests.
- Typed: add
bonusto the row's score the same wayFAVOURITE_BONUS(12) is added. Match tiers are 1000 apart on both sides, so usage cannot cross a tier, cannot beat a favourite on its own (12 > 8), and cannot reorder entry types (the launcher'sTYPE_WEIGHTgaps and ours are wider than a tie). - Untyped (the palette's empty query; the launcher's bare
>/?browse): sort by context tier, then favourite, thenbonusdescending, then title. On the launcher side session verbs still lead that list.
Every open TD runs FNSTools, so several processes write consumers.fnstools.
A block written from memory would clobber the other TDs' increments. So:
- Re-read the file at write time.
- Apply the run update above to that one key in the value just read, prune the writer's own block, and leave every other block exactly as read.
- Write atomically (temp file in the same folder, rename over), retrying the rename a few times tens of milliseconds apart for Windows sharing violations, as curation rule 3 does.
Two TDs recording at the same instant can still lose one increment. That is
accepted: usage is a tie-breaker, and a lost run costs at most one run's
worth of rank. The launcher is one process with one Rust writer for both of
its windows, so tdxlu really is single-writer.
A file that will not parse is parked (<file>.corrupt-<epoch>) and the
consumer carries on with no bonus. The next recorded run writes a fresh
document holding only that key. Usage is not worth rebuilding from memory.
Two consumers can each park the same corrupt file, leaving two
.corrupt-<epoch> copies; that is harmless, since nothing in either was
readable. Only a parse failure parks. A read refused at the OS level (on
Windows, a sharing violation while another writer holds the file open) is
not corruption: that write is skipped and the file left alone. Raised by the
launcher's agent from their implementation; the palette side had parked on
any read error until the same day.
- FNSTools: a
Rankbyusagetoggle on FNS_CommandPalette, roaming with the tool's config host (global scope follows the user between projects). - TDXLU: Settings > Quick Launch > "Rank by usage", in launcher config (per machine).
- Off means no bonus; recording continues.
- Clear usage removes only the clearing consumer's own block. Ranking sums all blocks, so the UI copy says so, for example "Clears FNSTools' usage history; the launcher's still counts."
- Default: ON on both sides (owner, 2026-09-23). The bound keeps it a tie-breaker.
Contract approved by the launcher's agent on 2026-09-23, which recomputed all seven test vectors independently and asked for the writer half of the schema rule (folded in above). The owner set the default to ON the same day.
Palette side built 2026-09-23. CommandCuration.UsageFile holds the file
protocol beside CurationFile; CommandPaletteExt records in _runCommand
after a reply that is not ok: False (command rows by their ident, presets
by their target command's tool#id), adds the bonus in Rank, and offers
UsageBonus(key) and ClearUsage(), the latter behind a Clear usage button
on the Commands tab. The temp file is named per process (.<pid>.tmp) so two
TDs writing at once never share one. tests/test_command_usage.py runs the
seven test vectors and the file rules against a scratch palette. Verified
live: five recorded runs lifted a general command from row 74 to row 15 (the
top of its tier) with a bonus of 4.7082, the toggle off restored row 74, a
refused run recorded nothing, and Clear usage left an empty consumers.
Implemented on both sides. Palette d0075aad (FNSTools_PRIV), launcher
be817c2 (TDXLPP dev, pushed), both 2026-09-23.
Launcher side built 2026-09-23. src-tauri/src/command_usage.rs
in TDXLPP, writer tdxlu: the same re-read-and-update-one-key write, a
per-process temp file, the rename retried 5 times at 40 ms, and a
process-wide mutex over both launcher windows. It records in runToolCommand
after a reply that is not ok: false, and presets record under their target
with the @instance pin dropped. Its 18 tests cover the seven vectors and
the file rules. Settings > Quick Launch > "Rank by usage" is on by default,
and its two-click Clear usage says FNSTools' history still counts.