Repository navigation
ux: clearer menus — X conflict fixed, confirms, refusal params, resume card, give counts, garage names, money format - #90
Merged
Merged
Conversation
…ding the call The progress bar's cancel and a call's decline/hang-up are both X out of the box, and the host fires every mapping on a key from one press, in an order nobody promises. Cancelling an eat or a repair while on a call therefore also hung up the call (or declined the one ringing). The rule now: a cancelable bar that is up takes the press. The calls key asks the progress contract first. If a cancelable bar is up on the same key, the press is the bar's. If the bar's handler already ran and the bar is gone, a cancel in the last 300 ms counts as this press too. With no bar, or a bar that cannot be cancelled, or the two bound to different keys by the player, X declines or hangs up exactly as before. `progress.State` carries the two facts this needs (`cancelable`/`cancelKey` of the bar that is up, and when the key last cancelled one). Tested with the two handlers fired in both orders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…usal toast goes
`OPX.Refuse` could not carry params, so a module whose sentence names
something followed it with `OPX.NotifyLocale`: two toasts in two lanes, and
the refusal's copy read `{garage}` or `{total}` with the placeholder left in.
It takes `params` now (fifth argument, so every existing call is unchanged),
bounded to plain strings and numbers under plain keys -- a refusal still
carries no internals by accident -- and the client renders the sentence
with them, checking them again.
The inventory's `refuse` was the last helper sending both; it sends the
refusal alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd on the eye
Menus: a row may carry `confirm` (true, or `{ title, yes, no }`). The menu
contract turns it into a two-row level whose FIRST row keeps things as they
are, so Enter pressed without looking does nothing, and whose second does
what the row said under the same id and data -- the caller hears exactly
the select it always heard. The shops' outfit delete, which built this by
hand, uses it.
The eye: a `danger` row now asks for a second click unless it says
`confirm = false` (any row may opt in with `confirm = true`). The first
click relabels the row `Confirm: <label>` in place, inside whatever folder it
sits in; only a second click on the same row within four seconds runs it.
The staff `Remove (staff vehicle)` ran on one stray click and now asks. Kick
and Ban opt out: they open a form, which confirms on its own.
web/index.html rebuilt for the page half of the eye's confirm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`Body` opened a level holding a single disabled row, and `Outfits` a level holding a single row whose label was a sentence: two presses to read one fact, two to open the fitting room. The body type is now a disabled row on the top level with its note as the description, and `Open fitting room` is a top-level action. Their old labels go from both catalogues. A test opens the panel and requires that no level of it holds a single row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Twenty-six keys, in both languages, that no Lua, Vue or TypeScript file reaches -- each grepped for as a literal and checked for a dynamic prefix before it went: - calls: the incoming card and live chip (`calls.incoming.*`, `calls.live.*`), removed with the eye rows they named -- one of them still told players to "Hold ALT on yourself to answer" -- plus `holo.tab.nearby` and the duplicated `holo.noNear` / `holo.noneNear`; - target: `target.hint` and `target.back`, sent to the page and drawn by nobody; the page's label type loses them too; - inventory: `inventory.title`, `inventory.ui.slots` (and its slot in the labels payload); - shops `outfits.code`, vehiclekeys `given`, garages `notYours`, appearance `title` and `command.wardrobe`. `menu.key.*` stays: the menu contract's public `Keys()` answers them. web/index.html rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ncy's word Prices reached players as bare numbers -- `Paid 4500 at Jinguji.`, `You cannot afford that: 4500 needed.`, a crafting row's `2:00 1500`, a hauling payout, a uniform's price -- or with the configured code (`500 BANK`), and the one formatter that existed hard-coded `€$` and a space separator. `OPX.Locale.Money(amount, moneyType)` is the one door now: the thousands separator is the language's (`4,500` in English, `4 500` in French with a narrow no-break space), the currency is a catalogue word (`€$`, `€$ (bank)` / `€$ (banque)`), and a type with no word is named rather than dropped. The character module's `FormatMoney` (and so every dealership price) uses it, as do shops, crafting, hauling and the paycheck. The grouping is done on a one-byte comma and swapped afterwards: `GroupDigits` reverses the string, which would have reversed the bytes of the French separator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The garage list was a column of plates -- `8XK2L0P`, `4KD9X1A` -- and nobody recognises their car by its plate in two seconds. The server now sends each vehicle's model as a player reads it and the row says `Villefort Cortes · 8XK2L0P`, exactly as that vehicle's key is labelled in the bag. The record-to-name reading the vehicle keys already had moves to `OPX.Vehicle.DisplayName` in lib/shared, so the key and the garage cannot drift into two names for one car. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Give and Drop moved the WHOLE stack in one click: handing over one of twenty bandages meant splitting first, then giving -- three steps -- and a misclick gave away all twenty. A stack of more than one now opens the count dialog the split already had, titled `Give how many?` / `Drop how many?`, starting at one; its confirm says `Give` or `Drop`. A single item still goes in one click, where there is nothing to choose. The dialog is the confirm step too. The give list read `#12` -- a server slot -- for each person in reach. It now reads their character's name, falling back to the slot only when no character is loaded. The nearby answer used to be "never a name"; this is the owner's call, and the list still holds only the people within arm's reach, shown only to the one about to hand them something. web/index.html rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd wait A returning character offered the spawn menu (OFFER_POLICY 'always') could keep its position only by picking nothing and waiting the timeout out: the page never said so, and Escape did nothing. The server now tells the client whether the character's row holds a position, and the client puts a first card, `Where I left off` / `Là où j'étais`, before the configured places. Choosing it settles the choice with no point -- the row decides, which is the resume -- and the toast says `Back where you left off.` A character that has never stood anywhere is not offered the card, and asking for it anyway is refused with the menu left up. A configured place may not take the card's id. The page needs no change: the card is a location like the others. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page answers a missing key with the key itself, which is right once the catalogue is there -- a key that does not exist should be loud -- and wrong before it: the catalogue arrives in parts of 250 a frame, and on a cold join every label not yet delivered painted as its raw key (`hud.voice.state.idle` on the HUD, `calls.holo.title` in the sphere). - The store marks the catalogue ready only when its last part has landed, and until then `useLocale.t()` draws nothing for a miss: a blank label for a frame or two, never a key. After that, a genuinely missing key is still drawn as the key. - Parts are gathered aside and swapped in whole, so a language change no longer empties the live labels for the frames between its parts. A source-level check holds both, since the suite has no browser. web/index.html rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # modules/progress/client/main.lua
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.
Follow-up to #88, implementing the coordinator's decisions on its proposals. Item 1 (menu titles and breadcrumbs) is skipped on purpose: the header-less menu is the design the owner approved.
core/*/exports.luaand the contracts are untouched. origin/main, including #89, is merged in.Before → after
3. The X key (high priority)
progress.Statenow also reportscancelable,cancelKeyandcancelledAtMs.4.
OPX.Refusetakes paramsNotifyLocale. The player got two toasts, and one of them still showed the{placeholder}.OPX.Refusetakes a fifth argument,params, so existing calls are unchanged. Params are limited to plain strings and numbers under plain keys, capped at 8 entries and 64 bytes per value. The client re-checks them before rendering.NotifyLocalewas in the inventory; it now sends only the refusal.2. A confirm option for menu and eye rows
confirm = true, or{ title, yes, no }. The menu turns it into a two-row level:dangerrow now asks for a second click, unless it setsconfirm = false. Any row can opt in withconfirm = true.Confirm: <label>in place, inside its folder. Only a second click on the same row within 4 s runs it.Remove (staff vehicle)used to run on one stray click; it now asks.5. "Where I left off" on the spawn screen
Where I left off/Là où j'étais.6. Inventory give and drop
Give how many?orDrop how many?and starting at 1. Its confirm button saysGiveorDrop, and the dialog doubles as the confirm step. A single item still goes in one click.#12for each person; it now shows their character's name. The old code said "never a name"; this follows the owner's decision. The list still contains only people within arm's reach, and only the giver sees it.7. Garage list
8XK2L0P.Villefort Cortes · 8XK2L0P, the same label as that vehicle's key. The name derivation moved toOPX.Vehicle.DisplayNameso the key and the garage list cannot drift apart.8. Money formatting
Paid 4500 at Jinguji.,2:00 1500,500 BANK.OPX.Locale.Money(amount, type)formats every amount:4,500in English, and in French4 500with a narrow no-break space.€$, or€$ (bank)/€$ (banque).FormatMoney, and through it every dealership price, plus shops, crafting, hauling and the paycheck.GroupDigitsreverses the string, so grouping directly with the multi-byte French separator would have reversed its bytes. Grouping happens on a one-byte placeholder that is swapped afterwards.9. Appearance panel
Bodyopened a level holding one disabled row, andOutfitsopened a level holding one row.Open fitting roomas the action.10. Unused locale keys
holo.tab.nearby, and the duplicatednoNear/noneNeartarget.hint,target.backinventory.title,inventory.ui.slotsoutfits.code, vehiclekeysgiven, garagesnotYours, and appearancetitle/command.wardrobemenu.key.*, because the menu contract's publicKeys()still returns them.11. No raw keys before the translations arrive
hud.voice.state.idle.Checks
OPX_LIB_PATH=… lua tests/run.lua: 4646 checks, 0 failed (after merging main).OPX.Locale.Moneyin English and French, multi-groupluac -pon every Lua file,tools/check-sandbox-globals.py, andnpm ci && npm run typecheck && npm run buildall pass.web/index.htmlis rebuilt and committed.Notes for review
danger = truenow confirm by default too, through the creator surface merged in Creator surface: the reads, runtime items, use handlers, a judged bar, benches, eye rows #89. That is intended. A resource that wants one click must setconfirm = false.🤖 Generated with Claude Code