Repository navigation
Conversation
setLanguage called activate(), which emits, and wrote the choice to localStorage afterwards. useI18n() reads personalLang() while it renders, so right after a switch the person had no choice of their own and "Use the instance default" was missing until a reload. activate() now runs a persist callback after the catalogue has loaded and before it emits; a catalogue that fails to load still saves nothing. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… changes
The apps (names, tile order, top bar, sidebar header, launcher, command palette) came from a
useMemo that listed the modules, the settings and isAdmin but not the language, so after a live
switch they kept the old language until a reload. Add lang and locale to its dependencies.
The name field of the Apps tab in Customize is uncontrolled (defaultValue) and a browser keeps
what a field was made with, so with the list rebuilt a plain focus and blur saved the old
name for everyone as a custom name. key={a.name} makes the field again when the name changes.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…n record The memo of the smart buttons (titles of the related models, tooltips) listed d.models and the model name, so a switch with the account menu on an open record kept the old titles. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…r dependencies The command palette, the search suggestions of the control panel, the model choices of the module builder and the readable models of the widget form build translated texts in a useMemo. Only the module builder's could show the old language (its memo depends on the modules alone, so a switch with the account menu on that tab kept the old names); the others were saved by an input that changes on every render, or by the app list, which now changes with the language. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The server sorts text in the collation of the locale it is told (api.ts sends it at fetch time), but the keys of useServerRows, useAggregate and the Kanban columns did not hold the locale, so a list sorted on a text column kept the old order after a switch. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… the language
title={m.description} was English in every language; describeFor(m) picks the description of
the active language (all bundled models have one in de, fr, pl, fi, sv and es) and falls back
to the English one.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…age in its dependencies Reads the source of the apps and the design system, finds every useMemo and memo, and fails when the function it is given calls t, tx, a label helper, compareText or a fmt formatter and the dependencies name neither lang nor locale. A memo that is right although it looks wrong (modules/view.ts: the texts are made by the handlers it returns) says why in an i18n-memo-ok comment. On main it flags seven memos: the app list and the smart buttons (the bug), the four fixed in the previous commits, and the exempt one. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…d language Swedish to English on an instance whose default is Swedish and English to Swedish: app names and tile order equal those of the reloaded page, launcher, command palette, sidebar header, aria label and tooltip, top bar, and "Use the instance default" at once. With the account menu: the Apps tab follows and leaving a name field writes no setting (a real rename still saves), the related-record buttons of an open record and a list sorted on the server follow. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…d parse The dashboard lists sort equal positions by title with the collation of the region, the widget card writes its "not set up correctly" and "no access" texts in resolve(), and the list plan reads range bounds with the decimal separator of the region. All three were memoised without the language, so a switch without a reload kept the old order and the old text. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…call graph The guard checked a memo against a hand-written list of names, so it missed every function that translates only through another one (byPosition sorts with compareText, resolve writes texts with t) and a function handed on without a call (.sort(byPosition)). Of the fourteen memos that named the language in their dependencies at 911a975, eight would have passed with it removed. i18nGuard.ts now reads every file of the apps and of packages/ui, resolves imports and re-exports, and takes as translators the functions that use a reader of the i18n store, directly or through others: by call, by reference, as a JSX tag, or as a member such as d.recordLabel and d.refText. i18nDeps.test.ts checks the reader with fixtures, pins the set of translators on the real tree (a missing link fails the test), fails when a store function that reads the language is not classified, and takes the language out of each of the eighteen memos that name it now to see that every one is then flagged. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…yped next The name field of Customize > Apps was keyed by the app's name, so it was made again when its own rename was saved; keystrokes typed after refocusing in that moment were lost. It is a controlled field now: it shows what is typed and not yet saved, else the app's name, so it follows a change of language, a reset of the launcher and a rename saved elsewhere without being made again. The e2e spec for the Apps tab gets stricter in the same change: - the request spy matched an address that ends in /api/setting/update, which the app never sends (it adds ?lang=..), so "no setting was written" could not fail; it matches the path now, and the single write of the spec is the rename, so a write on blur would show as a second one; - the Swedish app name comes from /api/meta (the module's label) as the page builds it, not from a word typed into the test; - no fixed wait before a negative assertion; the order of the writes decides; - the field is the same element after its own rename is saved, and a reset of the launcher puts the name of the language back. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…the request carries the language api.ts puts lang on a search only when it has an order, so a list or a board column without a sort gets the same answer in every language, and the locale in the query keys of useServerRows and the Kanban columns made it fetch the same page again for nothing. The locale is part of those keys only when there is a sort. useAggregate asked again on a switch too, but every consumer builds its own order from the groups (the pivot, the graph, the board totals and the widgets all sort or index them), so the change did nothing; it is back to the version on main. Two e2e tests: a list without a sort sends no request on a switch (the next one is the sorted one, in the new language), and a sorted board puts the cards of a column in the order of the new language. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ks again After a language switch a sorted list asks again, and the open record can be missing from the new first page. useRecord then had nothing for a render (the get that fetches it has not answered), ModelPage dropped the record panel, and whatever was typed in it was lost. useRecord remembers the last row it returned for the id and keeps returning it until the get answers; a record that is gone (the get says so) still closes the panel. The e2e test opens a record that is on the first page in English and on the second in Swedish, types in it, switches the language and checks the panel and the draft are still there. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The panel kept finished sentences in state: the text of a field that failed its check or was refused by the server, and the banner for a refused move or action. The panel stays open through a language switch, so they stayed in the language they were written in. It keeps what the sentence is made of instead: which fields failed the check (the sentence is made again from the value, by the same check, when drawn), the server's own words for a refused field and for the banner (serverMessage words them when drawn). moveRecord returns the server's words as raw next to the sentence for its callers that show it at once. Two e2e tests: a field that failed its check and a refused move are in the language of the page after a switch. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…h still leaves The language bullet of WORKFLOWS-LISTS-DASHBOARDS now states the two rules (a memo that translates names the language; state does not hold a finished sentence), what i18nDeps.test.ts reads and what it does not, and that a query without a sort is not asked again. ISSUES D8 to D14: toasts and the confirm dialog keep their text; error texts kept in state on the sign-in, dashboards and device screens; the module builder's seeded sample words; a decimal draft read with the separators of the language active at Save (same at 32a0758, scenario measured, not fixed); the double catalogue fetch at load; the Windows end-to-end harness; the blind spots of the guard. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Opened by the address, the record is fetched on its own at load and kept, so it survives leaving the list's page whether or not useRecord remembers the last row; the test passed with that change taken out. It opens the record from the list now, as a person does, so only the list's page knows it. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The test took its handle on the field after the rename was saved, by the label
that follows the new name, so it always found the new element and could not see
a field that was made again. It takes the handle first and checks it is still
attached afterwards; with the key of the first version of the fix
(key={a.name}) it fails there.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…glish The server's English is what the message rules match, and the English wording of a rule is close to it, so a sentence kept in English was worded in Swedish again by serverMessage and the test passed with the banner storing finished text. A sentence kept in Swedish is not worded in English by anything: the test now provokes the refusal in Swedish, switches to English, and back. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… is read the same everywhere) The comment claimed a range bound is read with the decimal separator of the region. rangeNum and rangeDecimal take a dot or a comma in every locale, and the only reader of the i18n store is fmt.exponent() (the digits of the instance currency), so the memo is right without the language. Drop the unused locale dependency and mark the memo with the true reason. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ion, member receivers - B-1: a name is shadowed only when the code around the hook declares it (constants above it in the same blocks, the parameters of the function that holds it), not when any callback of the component names it. A function declared inside another no longer counts its own name as a use of the import of the same name. - DG-2: a declaration is an arrow function when its => sits at the depth of the declaration, so a ; in an object type does not cut it short. - P-generic: the deps of a memo are the tail after the last top-level comma, so new Map<string, string>() in the body is not read as deps. - DG-4: the marker counts in a comment directly above the statement or inside the call, and needs three word characters of reason. - DG-3: a member translator is followed on d, lk and lookup only, so hash.update(x) is not d.update(x). - UT-6: the floors are 58 hooks seen and 16 carrying the language. - B-4: the comment on exported() no longer claims export default is followed. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…gone The last row seen is kept so an open record panel is not unmounted while the sorted list asks again. When the get answers "no such record" (deleted, or no access) nothing is left to keep, and the old row must not come back while the same id is fetched again. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…o and useI18n, known limits - DB-4: every request carries ?lang= and Accept-Language; an unsorted search lacks the body lang that picks the collation (useServer, KanbanView, WORKFLOWS-LISTS-DASHBOARDS). - DB-5: resolve() writes the detail sentences of an invalid tile, not the titles drawn by the card. - I18nProvider comment: a memo component is fine if it calls useI18n(); the old text said never to use memo. - ISSUES D9: more files with finished sentences in state, and a refusal the server already worded; D13: Git Bash path conversion; D14: the shapes found missed, the receiver rule, the marker rule, the lang and locale equivalence with the memos that show it. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…another record New cases in language-switch.spec (15 tests, was 10): - the refusal of a move made with the Move to menu (GovernedValue), of an action button (act()) and of a field the server named (the refused map) are each shown in Swedish, then English, then Swedish again, so a sentence stored in the language it was worded in fails; - a board with no sort asks for nothing when the language changes; - a record that is not on the page yet does not show the remembered row of the one before it while its get is held back. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
An apostrophe in JSX text blanks the rest of the line, and a slash right after a closing brace is read as the start of a regular expression, so a t() after either on the same line is not seen by the guard. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ument, block comment markers
- F1: scopeAt takes the parameters a header binds (and the variable of a
for-of), not every (a) => in it, so a function type in an annotation
({ f }: { f: (t: string) => void }) no longer hides the import.
- F2: a declaration of the translator graph shadows with its own
parameters and the constants at the level of its body, the same rule;
a callback's parameter or a function type no longer hides t (the
CodeBlock of packages/ui is a translator again).
- F3: what follows the last top-level comma is the dependencies, unless
the text before it is not yet a whole argument (type arguments), so
deps, deps! and a conditional are refused as "not a list" again.
- F4: a block comment is a marker above the statement only when it has
its lines to itself; a trailing one belongs to the line it follows.
- Fixtures for each, for the 3-character reason rule, for a const in a
sibling callback or an if block, for t => { ... }, and a real negative
for Object.create.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…get returns nothing The remembered row of 9a9aa91 was forgotten only after a get had answered null. A record deleted from its panel was never fetched again, so on Back to its address the panel opened with the old data until the get answered (and a record opened by address kept its fetched row the same way). The rule is now a small pure module, lastRecord.ts: the row is kept while the get is on its way, replaced by a newer one, and forgotten when the route holds no record or when the get does not return the record (gone, no access, or failed). useRecord also drops its fetched row when the route holds no record. Unit tests for the module. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…oes not come back with Back - The unsorted-board test looked for an aggregate command that is not on the wire (an aggregate is a search with an aggregate key in the body). Requests are now classified by body, and anything the sort does not explain is listed in the failure. - A record deleted from its panel and opened again by Back (its get held back) shows no panel; the record is off the first page and opened by address, so both the remembered row and the fetched one are exercised. - The held-get test (the record of another id) waits for the get before it looks, so it does not depend on when the render happens. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…D13, D14 - WORKFLOWS-LISTS-DASHBOARDS and the useServer and KanbanView comments: every command (call in api.ts) carries ?lang= and Accept-Language; the body lang is added by search() with an order and always by aggregate(), which does not matter while only select, ref, bool and date fields can be grouped, because the views order the keys on the client. - D9: PlanDialog keeps the finished sentence in its install and uninstall dialogs (only the preview error is raw); connectors/Catalog.tsx and Sections.tsx added; the search is described as including helpers that return a sentence. - D10: the sample fields are one useState, only their descriptions are translated. D13: the value Git Bash gives, and what the build writes. - D14: the limits probed in round 4 (statement start and commas, parameters of expression bodies, type parameters that hold a function type, constants in inner blocks of a graph function). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…tions the same way Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This branch has not been deployed
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.
Switching the language in the back office without a reload left app names (and their order), the top bar, the sidebar header, the command palette and a few other texts in the old language; "Use the instance default" was missing until a reload. This fixes the causes, each confirmed in the code and in the running app, and adds tests that fail on
main.This description was rewritten after the review of 911a975 (round 2) and corrected again after the reviews of c8131b4 (round 3) and 9a9aa91 (round 4). Sections "Round 2" to "Round 4" say what changed; the rest is corrected where an earlier version was wrong.
What was wrong and what changed
App.tsxapps = useMemo(buildApps(...), [modules, settings, isAdmin])had no language in its deps;buildAppscallsappTitleandcompareText, so names and order stayed oldlang,localefromuseI18n()added to the depsstore.tssetLanguagecalledactivate()(which emits) beforelsSet/writeCookie;useI18n()readspersonalLang()while renderingactivate(lang, persist?)runspersistafter the catalogue has loaded and beforeemit(). A catalogue that fails to load throws beforepersist, so nothing is saved. A pseudo-locale is still never saveddefaultValue); a browser keeps what React first put into a field, so after change 1 the field kept the old-language name whilea.namechanged, and a plain focus + blur saved it as a custom name for everyoneCustomize.tsx). (Round 1 keyed the input bya.name, which made the field again when its own rename was saved and lost keystrokes typed at that moment.)RecordPanel.tsxrelatedmemo had[d.models, model.name]d.langaddedcallinapi.ts) carries?lang=andAccept-Language; in the body,search()adds thelangthat picks the collation only when the search has anorder, andaggregate()always adds it. So a sorted list or a sorted board column depends on the locale; an aggregate does too in principle, but only select, ref, bool and date fields can be grouped (GROUPABLE) and the views put the keys in order on the clientuseServerRowsand of the Kanban columns only when there is a sorttitle={m.description}was English in every languagetitle={describeFor(m)}. Measured (see below): every model of the 23 bundled packages and of the 8 built-in modules has a description in de, fr, pl, fi, sv, es. A module without label tables falls back to its English description, as everywhere elsedescribeForis usedRound 2 (after the review of 911a975)
DashboardsApp.tsx, two memos that sort equal positions by title) and the widget card (resolvewrites the detail sentences of an invalid tile, such as "A sum needs a measure field"; the titles "This tile is not set up correctly" and "No access" are written where the card draws). Each is one dependency.i18nGuard.ts+i18nDeps.test.ts) no longer checks against a list of names. It reads every file of the apps and ofpackages/ui, resolves imports and re-exports, and takes as translators the functions that use a reader of the i18n store, directly or through other functions: called, handed on (.sort(byPosition)), used as a JSX tag, or a member such asd.recordLabelandd.refText. At 911a975 it caught 6 of the 14 memos that carry the language in their dependencies; a test takes the language out of each in memory and now catches all of them (17 today). A second test pins the set (a missing link in the call graph fails it) and a third fails when a function of the store that reads the language is in neither the display list nor the not-display list.moveRecordreturns the raw message next to the sentence.InlineEdithas the same shape but is unreachable on a switch (leaving the cell closes it); the screens that still keep finished sentences are indocs/ISSUES.mdD9.useRecordhad nothing for a render,ModelPagedropped the panel and the draft with it.useRecordkeeps the last row for the id until thegetanswers. (This was a regression of round 1's locale in the query key.)langwith a sort; theuseAggregatechange is reverted (every consumer builds its own order from the groups, so it asked again for nothing); the Kanban change is kept, with a test.?lang=), so it could not fail. It matches the path now, and a write on blur makes the test fail (mutation Mblur).Round 3 (after the review of c8131b4)
ModelPage.tsxdoes not need the language. Rounds 1 and 2 said it did ("toServerreads range bounds with the decimal separator of the region") and addedlocale. That was wrong:rangeNumandrangeDecimaltake a dot or a comma in every locale (trim().replace(",", ".")), and the only reader of the i18n store intoServerisfmt.exponent(), the digits of the instance currency, which a language or region change does not change. The dependency is out again and the memo carries// i18n-memo-ok: <that reason>. (Side note, not changed: the currency of the instance is not in that memo's dependencies either; a currency change while a list is open is the same case.);inside an object type no longer cuts a declaration short; the exemption marker counts in a comment directly above the statement or inside the call and needs three word characters of reason; a member translator is followed ond,lkandlookuponly (hash.update(x)is notd.update(x)); the floors of the tree test are 58 hooks seen and 16 carrying the language (there are 60 and 17). Round 3 also changed how the dependencies of a memo are told from its function, and which names shadow in a function of the call graph; round 4 corrects both (below).useRecordforgot the remembered row when thegetanswered null. That did not cover a record deleted from its panel (nogethad run), and the comment called every null "no such record"; both are corrected in round 4.I18nProvidercomment said never to usememo, the docs say amemocomponent that callsuseI18n()is fine, and now both say the same; D9, D13 and D14 were extended. Round 3 said "?lang=andAccept-Languageon every request"; that was wrong (round 4).Round 4 (after the review of 9a9aa91)
scopeAttook every(a, b) =>in the parameter list of the component, so{ onPick }: { onPick: (t: string) => void }(the shape ofbrand/Iconography.tsx) puttinto the shadow set and at(...)in a memo there was not seen. It now takes the names the header binds (its parameters, the variable of afor (const x of ...)).CodeBlockinpackages/ui(it callst("Copied")and maps tokens with(t, j) =>) was not a translator anduseMemo(() => <CodeBlock .../>, [..])passed. A function now shadows with its own parameters and the constants at the level of its body, the rule of the hook.CodeBlockis in the pinned set.[or was a plain name, sodeps!,cond ? [a] : [b]andgetDeps()passed. What follows the last top-level comma is now the dependencies, unless the text before it is not yet a whole argument (the comma is in type arguments such asMap<string, string>), and a tail that is not a list is refused with "the dependencies are not a list the guard can read".const a = useMemo(..); /* i18n-memo-ok: x */followed by another hook exempted the second. A/* */counts as a marker above a statement only when its lines are its own, as//lines already did.const tin a sibling callback or anifblock above the memo,t => { ... }, three-line markers and a real negative forObject.create; the test header and the guard header describe the marker and the shadow rule as they are now. The limits the review probed and that stay are in D14: a marker above the first property of an object or the first of several declarators reaches the later ones, parameters of an expression-bodied hook or an anonymous default export are not read,<T extends (x: number) => void>(a: T) =>is not seen as a function, and a constant in an inner block of a graph function does not shadow.useRecordand Back. A record deleted from its panel closed the panel,laststill held its row, and Back to its address opened the panel with the old data until thegetanswered (a record opened by address kept its fetched row the same way). The rule is a small module,lastRecord.ts, with unit tests: the row is kept while thegetis on its way, and forgotten when the route holds no record or when thegetdoes not return the record. Anygetthat does not answer ok (an error reply, which for a missing record is a 400, a failure after the retries, an approval reply) counts as "not returned", not only "no such record". One thing stays: a record fetched by its address, left for one that is on the page and opened again while it is not, shows the row fetched before until itsgetanswers.aggregatecommand that is not on the wire (an aggregate is a search with anaggregatekey in its body), so two of its assertions could not fail. It classifies the requests by body now and lists any that the sort does not explain.modules/PlanDialog.tsxis not affected; its install and uninstall dialogs keepserverMessage(r.message)in state (only the preview error is raw), andconnectors/Catalog.tsxandSections.tsxwere missing from the list. D10: the sample fields are oneuseState, only their descriptions are translated. D13: what Git Bash gives (C:/Program Files/Git/admin/, measured). The statement about what a request carries is now exact (4b above anddocs/brand/WORKFLOWS-LISTS-DASHBOARDS.md).Tests
web/packages/ui/src/i18n/switch.test.ts(5 tests, unchanged): asubscribe()listener that readspersonalLang()sees the chosen language afterawait setLanguage(...); storage and cookie are written before the subscribers run; nothing is saved withpersist=false, for a pseudo-locale, or when the catalogue fails to load.web/packages/e2e/tests/language-switch.spec.ts, 16 tests, no reload between the switch and the checks: sv to en on thelanguageinstance (default Swedish) and en to sv on the main one (home tiles and their order against a reload, top bar, launcher, palette, sidebar header,aria-label, tooltip, "Use the instance default"); the Apps tab; the related-record buttons; a sorted list and a list without a sort (no request on a switch); a sorted board and a board without a sort (no aggregate and no unsorted search on a switch); a field that failed its check; the server's refusal of a status step, of a move made with the Move to menu, of an action button and of a field it named, each worded in the new language (Swedish, English, Swedish); a record opened from the list stays open with its draft when the sorted list no longer has it; a record that is not on the page yet does not show the row of the one before it; a record deleted from its panel does not come back with Back. App names come from/api/metaas the page builds them; there is no fixed wait.web/apps/backoffice/src/i18nDeps.test.ts: 30 tests (the reader on fixtures including a fake design system, transitive translators across files, aliases andexport *, identifier uses, member translators and their receivers, scope at the hook and in the graph, typed helpers, type arguments, dependencies that are not a list, the exemption marker,memo; the real tree).web/apps/backoffice/src/lastRecord.test.ts: 5 tests.What I ran
Windows 11, system Chrome, SQLite,
cargo buildof this branch (Rust is not touched),OBOP_BIN=target/debug/obop.exe.vp test: packages/ui 8 files, 172 tests; apps/backoffice 22 files, 339 tests (was 330: 4 more ini18nDeps.test.ts, 5 in the newlastRecord.test.ts); apps/platform 1 file, 7 tests. All passed.vp checkpasses in apps/backoffice (126 files linted), packages/ui (33), packages/e2e (54;vp run typecheckexits 0) and apps/platform (7);vp run i18n:checkin packages/ui: "catalogues ok";vp buildof backoffice (dist, anddist-adminwithMSYS_NO_PATHCONV=1), ui and platform.lastRecord.ts(tables below).getof a deleted record answers 400, which the console guard counts; the test now declares it withallowHttpErrors(400)); test 16 after the rewrite 3 of 3; the whole spec with--repeat-each=3: 48 passed (vp exec playwright test tests/language-switch.spec.ts; the exit status is 1 on Windows only becauseglobal-teardown.tshits EPERM, D13).vp exec playwright test, 7.4 min): 242 passed, 16 skipped (the QA sweep, off unlessOBOP_QA=1), 0 failed.web,e2eandlockfilespassed on the first attempt;rustfailed there inlimits_tests::an_agent_is_not_told_how_long_a_value_it_only_holds_as_a_token_is(limits_tests.rs:943), a test this branch does not touch (it asserts!e.contains("40")on a message that begins with the random module prefix), and passed on the second attempt, withall. The same test failed once before on the previous head and passed on a re-run.Mutations
Each one is a change to the source,
vp build(for e2e), the spec or the unit tests, then reverted.Rounds 1 and 2 (run against the 10-test spec of that time; the test numbers are those of today's 16-test spec, since tests were only appended; the rows were not repeated after rounds 3 and 4, which changed none of the code they touch except comments and
useRecord, whose mutations M19 and M26 are redone in round 4 below):store.tsas on main (subscribers told before the choice is saved)switch.test.ts("is saved before the subscribers run, so they see the choice", "is seen by a person who had no choice and one who is changing it"); e2e 1, 2lang, localeout of theappsdeps inApp.tsxApp.tsx:176m.descriptiond.langout of therelatedmemo of the record panelRecordPanel.tsx:290useServerRowskeykey={a.name}on the name field (round 1)defaultValueand no key (main)moveRecordas before round 2 (finished sentences in state)out.message)useRecordwithout the remembered rowlocaleout of the two dashboard memos,d.langout ofresolve,localeout of the list plan (round 2, when it had it)i18nDeps.test.ts"has no memo that translates, sorts or formats without the language", namingDashboardsApp.tsx:202,:210,WidgetCard.tsx:25langout of the palette, control panel, module builder and widget form memos; the exemption comment ofmodules/view.tsremovedChrome.tsx:238,ControlPanel.tsx:47,ModuleBuilder.tsx:195,WidgetForm.tsx:60,modules/view.ts:107getLangout of the seeds of the readeri18nDeps.test.tsformatLocaleout of the seedsi18n-memo-ok:accepted as a reasonmemocomponents not readRound 3, e2e: five source changes applied together to the committed head (
mutate.sh: change,vp build, the 15-test spec,git checkout). Each failing test shows the signature of its own mutation. The other ten tests passed.RecordPanel.tsxGovernedValueonMove:setError({ raw: out.raw })toout.messageRecordPanel.tsxact():setError({ raw: r.message })to{ raw: message }RecordPanel.tsxfailure():[f.name]: messagetoserverMessage(message)..._note.code must be at most 10 characters; this has 11, received "Kod får vara högst 10 tecken långt; nu är det 11"KanbanView.tsxbaseSig:order.length ? locale : nulltolocaleorderuseServer.tsuseRecord: drop&& last.current.id === idgetof the other record is heldRound 3, guard: the old behaviour of one fix restored at a time in
i18nGuard.ts(gmut.sh:sed,vp test src/i18nDeps.test.ts,git checkout).outer = scopeAt(...)tolocalsOf(the whole declaration)if (!nested) shadow.delete(d.name)toshadow.delete(d.name)tabove the memo);ends a declaration/=>/.test(init.split(";")[0])commas[commas.length - 1]tocommas[0]marked(...)back to the first version's regexRECEIVERto any identifierif (line % 7 !== 0) found.push(...)// i18n-memo-ok:out of the comment inModelPage.tsxModelPage.tsx:115(toServer)Not mutated: the
useRecordforget-on-null change (DB-2) has no test; the comment and docs changes.Two earlier mutations had survived the first version of their test and the tests were fixed: M19 (the record had been fetched at load by its address; the test opens it from the list now) and M17b (the English wording of a rule is close to the server's English, so a kept English sentence was worded in Swedish again; the tests provoke the refusal in Swedish and switch to English and back). The new refusal tests use the same Swedish, English, Swedish order, and the field test relies on
serverMessagereturning the server's own words unchanged in English.Survivors among the hunks that stay: none known. Protected only by the guard (no behavioural test): the dashboard, widget card and palette memos, the control panel suggestions, the module builder targets and the widget form. Two of them are modal (the palette, the widget form), so the language cannot be switched while they are open; the control panel suggestions and the module builder targets could be driven and no test does. No test:
useRecordforgetting the row on "no such record".Round 4, guard: the old behaviour of one fix restored at a time in
i18nGuard.ts(gmut4.sh:sed,vp test src/i18nDeps.test.ts,git checkout). Each fails exactly the tests named; the other tests pass.(a) =>in itdeclared(header)back tolocalsOf(header)inscopeAtparametersOf(...)back tolocalsOf(text)inown()CodeBlock)depth - base <= 1filter inown()const tinside a callback)truestartsWith("/*")check inmarked(){3}to{1}inREASON{3}to{5}why)scopeAtt => { ... }gives no parameterscopeAtObjectas a receiver of membersRECEIVERto(?:d|lk|lookup|Object)Object.create(null)is the only line it can change)Round 4, e2e: one source change at a time (
mutate4.sh: change,vp build, the spec restricted to the test with-g,git checkout).useRecordas in round 3if (row) last = ...; else if (row === null) last = nullgetis held; the remembered row is what it showssetFetched(null)KanbanView.tsxbaseSig:order.length ? locale : nulltolocalelanguseRecordshows no remembered rowlastRecord.tsremembered: returnundefinedremembered: droplast.id === idgetof the other record is helduseAggregate.ts:d.langadded to the keylang: "sv-SE"Round 4, unit (
lastRecord.ts,vp test src/lastRecord.test.ts,git checkout):getkeeps the rowreturn nulltoreturn lastforrow === null!now.id || now.id === "new"to!now.id, returninglastnewid)last.id === idlast.model === modelreturn last ?? {...}Not mutated: the comments and docs; the
i18nGuard.tsheader.Measured: model descriptions in six languages
An instance with the 23
packages/*.tomlinstalled (obop modules install), a user with every group,GET /api/meta: 23 packages, 73 models, and the 8 built-in modules, 23 models; every one has a non-emptydescriptions[lang]different from the English one in de, fr, pl, fi, sv and es (0 missing, 0 identical, per language).Corrections to earlier descriptions
useCallbackhits are lazy" was wrong: four are;SecurityAccount.tsxload(about line 433) stores translated text in state (problem). It is indocs/ISSUES.mdD9. The guard still does not readuseCallback(D14).docs/ISSUES.mdD8 to D14 list: toasts and the confirm dialog keep their text (D8); error texts kept in state on the sign-in, second-factor, dashboard, upload, update, install, connector and device screens (D9); the module builder's seeded sample words (D10); a decimal typed before a switch is read at Save with the separators of the language active then (D11); the catalogue fetched twice at load (D12); the Windows e2e harness (D13); the blind spots of the guard, including thatlangandlocaleare accepted for each other (D14).modules/PlanDialog.tsxis not affected, that?lang=andAccept-Languageare on every request, and thatuseRecordforgot the row when thegetanswered "no such record". The first is wrong for the install and uninstall dialogs, the second for/api/meta, sign-in, uploads and the account endpoints, and the third left the deleted-then-Back path open (round 4).Not verified
default-languagespec covers the sign-out path).locale) are covered by the dependencies and the guard, not by a test that changes the region alone. At least five memos name onlylangand sort with the collation (D14), so a region change alone probably leaves their order; read from the code, not driven.🤖 Generated with Claude Code