rules editor: TypeScript support - #1202
Open
evgeny-boger wants to merge 11 commits into
Open
Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 298 |
| Duplication | 6 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
evgeny-boger
added a commit
that referenced
this pull request
Aug 15, 2026
- H1: type-aware completions actually surface the wb-rules API now. valtown's completion filter drops every ambient global (sortText "15") not on its hardcoded standard-JS whitelist, so defineRule & co never appeared once the language service loaded - and its non-empty answer shadowed the static sources, a regression for .js files too. Pass keepLegacyLimitationForAutocompletionSymbols: false and merge the snippet templates into the service's answers (snippets replace the plain entry of the same label; member accesses stay service-only). - M1: device/topic completions read the devices store at completion time instead of being snapshotted when the (memoized) extension array is built - devices arriving over MQTT after page load now show up in dev["..."], getDevice(...), publish(...) lists. - M2: the rename uniqueness pre-check now tests the path the rename will actually target (a .ts rule keeps .ts; only a fresh save defaults to .js) - renaming foo.ts to an occupied extensionless name no longer slips past the check and silently fails in the engine. - M3: typing the title of an unsaved rule no longer re-runs the language-service effect per keystroke (each run cost an Editor.GetTypes RPC); the effect is keyed on the stable service path. - L1: controller verdict diagnostics survive CRLF rule files - compare the checked content LF-normalized, matching CodeMirror's own normalization on ingest. - stale comments updated (the service runs for .js files too). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AoTGMFABCCENSPaHThxAru
evgeny-boger
force-pushed
the
rules-editor-typescript
branch
from
August 19, 2026 15:17
f1f34a3 to
79bd635
Compare
evgeny-boger
force-pushed
the
rules-editor-typescript
branch
2 times, most recently
from
August 20, 2026 14:57
b22ba95 to
4055bb9
Compare
evgeny-boger
commented
Aug 20, 2026
evgeny-boger
left a comment
Member
Author
There was a problem hiding this comment.
смотрел, но плохо
evgeny-boger
force-pushed
the
rules-editor-typescript
branch
3 times, most recently
from
August 21, 2026 09:24
1fdf1db to
853f874
Compare
ninedev-i
approved these changes
Aug 21, 2026
…etions A TypeScript language service runs in the browser for rule files, built on a virtual FS seeded with the wb-rules API declarations (vendored wb-rules.d.ts, generated from the engine; a later commit lets the controller supply its own copy). Seeding and every incremental update are LF-normalized: CodeMirror documents are LF-only, and a stray CRLF in stored file content would shift every diagnostic and completion position after it (pinned by the CRLF test). Completions are type-aware and merged with the static sources: the device/topic string contexts (dev["...], getDevice(...)) answer from the live device list first, then the language service, then snippets and the generated global signatures as the no-service fallback. An empty service result must not shadow later sources - except inside a string literal, where junk globals are worse than an empty popup. Member accesses and string-literal completions stay the service's alone. buildControlsRegistry turns the device list into declarations so dev["device/control"], getControl(...) and changed(...) know each control's value type and complete the existing names. The diagnostics linter filters and rephrases the raw TS output for rule authors, including the Promise-misuse family: a promise used as a condition, a forgotten await in an endless loop, await of a non-promise. globals-generated.ts is produced by scripts/generate-wb-rules- completions.mjs from wb-rules.d.ts; a drift test regenerates it through the script and diffs the result against the committed file byte for byte. The generated file and the vendored declarations are excluded from eslint and from the app tsconfig. The typescript package moves to dependencies (it now ships in a lazily imported editor chunk); TsModule is passed around as a type-only import so the heavy package stays out of everything the entry pulls in. stores/rules/types.ts gains the shared wire types (TsCheckResult, TsCheckDiag, LocalTsDiag, RuleRuntimeError) that the service, the diagnostics UI and the store wiring build on in the follow-up commits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvpkTQYSS1c78EpBCpLTpB
…, gutter, problems panel, runtime errors Squiggles that only speak on hover are useless on a controller touchscreen. diagnostics-ux.ts renders every diagnostic as a short message after the offending line (an error lens), a marker in the lint gutter, and a problems counter that opens the panel (F8 jumps to the next problem). The load error of the file joins the same gutter as one more lint diagnostic - one marker column, not two competing ones. The diagnostic sources that feed it are mobx-reactive linters: controller-diagnostics renders the controller's own check verdict de-duplicated against the local language service (same line and message means the local squiggle already covers it), and held back while the buffer has unsaved edits - a verdict for old content must not mark new lines. load-error anchors the engine's in-band Save/Load error. runtime-errors renders errors from the rules console (a rejected control write, an exception with a file:line) inline at the line the engine attributes them to; repeats of the same error at the same place are counted, not duplicated. runtime-error-parse extracts those locations from the console messages - paths survive spaces, parentheses and unicode because a path is everything up to the first .js/.ts before the line number, anchored on the at that precedes every engine-reported location. lint-refresh is the shared plumbing that forces a lint pass when a store value changes rather than when the user types. CodeEditor stops rebuilding its extension stack on every keystroke: the underlying component reconfigures whenever extensions or onChange change identity, and pages passing inline handlers re-render per keystroke. onSave/onChange are bridged through refs (the extension effect depends on whether a save handler exists, not on its identity), and useAsyncAction keeps the wrapped action in a ref so execute has one identity for the component's lifetime while still calling the latest action. Tests pin the contract: a rerender with new inline handlers hands CodeMirror the same props, Mod-s still runs the latest handler, a genuinely new extensions prop still rebuilds. These modules are self-contained here; the rules page starts feeding them in the next commit. Locale keys for the problems counter and the load error marker come along. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvpkTQYSS1c78EpBCpLTpB
The edit page builds the full editor out of the previous commits. Editor.GetTypes doubles as the feature gate: firmware that does not advertise the method gets a plain editor - no language service and no download of the heavy TypeScript chunk, because a legacy engine must not get completions and promise semantics it does not have. On advertising firmware the controller's declarations seed the service, the vendored copy is solely the fallback for a transient GetTypes failure, and the chunk import overlaps the GetTypes reply. The language service is loaded with a dynamic import, and the store side reaches it only through type-only imports - the app entry bundle stays free of CodeMirror and TypeScript. A service init resolving after navigation away is dropped; a reused service is reseeded so a reopened editor starts from the stored content, not from what was typed and discarded. The store polls the controller-side check (Editor.Check, with backoff and an attempt cap) for .ts and plain .js files alike, and records runtime errors from the rules console per file and line for the page to render. Save and external reload keep their ordering guarantees: the engine loads the new version of a file BEFORE it publishes /wbrules/updates/changed, so a load-time error of the new version arrives just ahead of the notification - the changed handler must clear only the errors of the replaced version, not the fresh one it just received. rpcHasMethod caches availability per target AND method: different services may well share a method name, and one advertisement must not answer for another target. A method advertised only after the first ask timed out becomes available for the session instead of staying "unavailable". rpc-has-method.test.ts pins the cache, the retained advertisement delivered synchronously at subscribe time, and the late advertisement; the page tests cover the GetTypes gate and the fallback; the store tests cover the check polling limits, the runtime-error recording policy and the reload race. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvpkTQYSS1c78EpBCpLTpB
Changelog entry for 2.247.0 describing the TypeScript rules editor. The wb-rules recommendation moves to 2.47.0~quickjs2 - the engine that serves Editor.GetTypes and Editor.Check and runs .ts rules; it stays Recommends rather than Depends because the editor degrades gracefully on older firmware (the GetTypes gate in the page). .codacy.yml excludes frontend/scripts from analysis instead of inline suppressions: the completions generator is build/dev tooling, not shipped code, and it writes to a caller-supplied output path by design (the drift test uses a temp file). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvpkTQYSS1c78EpBCpLTpB
evgeny-boger
force-pushed
the
rules-editor-typescript
branch
from
August 24, 2026 17:43
853f874 to
bc77f97
Compare
…boundaries The upstream master shipped 2.247.0 (local_time rename) while this branch also claimed 2.247.0, failing the version-bump check on the merge build; the earlier changelog merge had also glued two stanzas without a separating blank line. The branch entry moves to 2.248.0 on top of master's history taken verbatim.
The TS language service has the data (getCompletionEntryDetails,
getSignatureHelpItems) and CodeMirror has the slots (Completion.info /
detail, showTooltip), but the @valtown/codemirror-ts adapter wires
neither: its completions carry only { label, kind }, so the popup showed
just the name (e.g. device.writeChannel with no signature or docs), and
it ships no signature help at all.
ts-help.ts adds both from the same service:
- withCompletionDetails wraps the completion source so each entry gets a
one-line `detail` (its signature) and an `info` panel (signature +
JSDoc + @tags), fetched lazily per highlighted entry.
- signatureHelp is a StateField that shows a parameter-hint tooltip for
the call the cursor is inside, the active argument emphasised.
Wired into ts-language-service.ts (member completions keep the service's
own list, so the details survive withStaticExtras); styles in the
code-editor CSS. Tested against a real language service over a small
d.ts (ts-help.test.ts): the signature and JSDoc reach the panel, the
active parameter is marked, and no call means no tooltip.
The parameter-hint tooltip added in the previous commit showed on every cursor move and overlapped the autocomplete popup, and its per-keystroke effect dispatch made the editing feel jumpy. The completion popup already shows the full signature (getCompletionEntryDetails displayParts) next to the JSDoc, so the separate tooltip is removed. The completion info is now built lazily - only for the entry the user highlights - instead of a getCompletionEntryDetails call per entry on every keystroke, so a long member list does not lag the popup. Hover (tsHover) still gives the type under the pointer.
The error lens appends the message inline at the end of the line. On the line you are typing, that widget sits right next to the caret, so as the diagnostic appears the caret looks like it jumped to the start of the message (it does not actually move - verified: pushing diagnostics leaves the selection put). Matching VS Code, the lens is now suppressed on any line a cursor is on and reappears the moment the caret leaves it; the squiggle, the gutter marker and the hover tooltip still flag that line. The lens plugin recomputes on selection changes too. Test: no lens on the caret's line, the caret stays, and the lens returns when the caret moves.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZtLwwQGQLcuL7XsxJicyo
Member
Author
|
ИИ: Комментарии порезал: в добавленном коде было 620 строк комментариев, стало 240 (2dc05f4, только комментарии, тесты 330/330). Оставил в 1–2 строки только неочевидное — контракты порядка сообщений в MQTT, формат локаций из движка, инварианты кэша TS. Плюс влил master (2.250.0 поверх вышедшей 2.249.0) — PR clean. |
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.
TypeScript-aware rules editor, paired with the TypeScript-capable wb-rules (wirenboard/wb-rules#223, #224). Four commits after four review rounds (history squashed; every commit passes tsc and the full 2585-test suite): the language service, the diagnostics, the store and page wiring, packaging. Size note: exceeds the 250-300-line guideline - the language service, its diagnostics sources and their tests form one reviewable unit.
rules editor: TypeScript support with an in-browser language service— error squiggles while typing, hover types and type-aware completions for.ts(and.jsviaallowJs), seeded with the controller's installed wb-rules declarations (Editor.GetTypes, vendored fallback). The controller's own background verdict (Editor.Check) renders inline, de-duplicated against the local check and suppressed once the document diverges from the checked content.rules editor: typed async API and registry-typed device references— the promise-native API andchanged()in the declarations;dev["dev/ctrl"]/getControl("dev/ctrl")/changed()typed against aninterface WbControlsregistry generated from the live devices store — the same mechanism the controller check uses.rules editor: warn on forgotten await (Promise misuse)— four custom diagnostics tsc doesn't provide: Promise as a condition, floating Promise in an infinite loop,awaitof a non-Promise, Promise written to a control.rules editor: check .js too, runtime errors inline, and diagnostics that do not depend on hovering—.jschecked with the controller-matching advisory policy; runtime errors the engine attributes to a line (control x/y: write ignored (…) at file:line, uncaught exceptions, watchdog aborts) become error squiggles on the open rule while it matches what runs; and every diagnostic is legible without hovering: inline error-lens messages, lint gutter markers, a Problems panel with a header badge (F8 / Ctrl-Shift-M). Includes a fix for a latent CodeMirror pitfall:forceLinting()is a no-op unless a pass is queued, so external diagnostics (the controller verdict too) never appeared until the next keystroke.Verification
.jscheckJs flaggingdev["buzzer/enabled"] = 123, runtime-error squiggle appearing on an idle editor when the rule misfires (with repeat counting and no tooltip flicker), problems badge/panel.After the reviews (rounds 3-4)
Editor.GetTypes.Editor.GetTypes- no language service (plain editor as before); the vendored d.ts remains only as a fallback for a transient GetTypes failure on advertising firmware.runningContentviaEditor.Load; CRLF files seed the language service LF-normalized; runtime-error locations parse paths with spaces/parentheses/unicode; the editor extension stack is identity-stable (no per-keystroke reconfiguration); per-target RPC method cache; overlapping saves are counted and a failed save restores the errors it cleared.debian/controlRecommendswb-rules (>= 2.47.0~quickjs2~~); project-rules conformance (types intypes.ts, index exports, new tests in new files).🤖 Generated with Claude Code
https://claude.ai/code/session_01BvpkTQYSS1c78EpBCpLTpB