Skip to content

v5.0.0 Major Refactor: Move from Vue 3 + JSDoc to Svelte 5 + Typescript - #84

Open
Sudo-Ivan wants to merge 127 commits into
masterfrom
next
Open

Sudo-Ivan wants to merge 127 commits into
masterfrom
next

Conversation

@Sudo-Ivan

Copy link
Copy Markdown
Contributor

Why? Better maintainability and opportunity to break down all those "god" files into components and more maintainable frontend. I am more experienced with Svelte and Typescript.

This will also feature large backend refactors to break down meshchat.py.

Using various open-weight models.

Ivan added 30 commits September 5, 2026 06:52
…to codebase brought to you by slopcoder5000 the worlds best model for massive refactors.
…nhance conversation viewer with contact filtering and telemetry item selection
…ing archive listing, viewer, and export functionality
…ive session, audio settings, contacts tab, history panel, overlay, phonebook, and phone tab
…agement, including dialer, active session, voicemail, and tab navigation
…t related references in ESLint and TypeScript configurations
…ainability, and improve archive routes with database checks
…mentation, and standardize TypeScript file extensions
…eadability and consistency across app shell and map features
…nd remote management across multiple languages
…om vue/js to svelte/ts and updating docs and other improvements/fixes
} else if (url.startsWith(":")) {
path = url.slice(1);
}
if (path.startsWith("media/")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test-f

out = out.replace(EXPLICIT_RE, (match, body) => geoAnchor(body.trim(), body.trim()));

// Maidenhead locators, 6+ chars only for auto-linking to avoid noise.
out = out.replace(MAIDENHEAD_RE, (match) => geoAnchor(match, match));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test-g

* Ported and simplified from meshchatx/src/backend/markdown_renderer.py
*/
static render(text) {
return cachedRender(text, MarkdownRenderer._renderUncached);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test-h

const info = readImage(container);
const index = container.getAttribute("data-mu-image-index");
post({
type: "image-action",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test-i

for (const key of NUMERIC_KEYS) {
const value = String(draft[key] ?? "").trim();
if (value) {
patch[key] = Number(value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test-j

Comment thread meshchatx/src/backend/page_node/core.py Outdated
quality = MEDIA_QUALITY
max_dimension = MEDIA_MAX_DIMENSION
if isinstance(data, dict):
quality = int(data.get("quality", quality))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Uncaught int() on remote-controlled quality

quality = int(data.get("quality", quality)) has no try/except, while the sibling max_dimension cast three lines below is wrapped. A remote peer sending {"quality": "high"} (or null) raises ValueError/TypeError inside the RNS response generator; every other remote-input parse in this class fails closed. Wrap it like the max_dimension cast.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

continue
return None

def _media_cache_key(self, source_path, quality, max_dimension):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Unbounded, remote-controlled media cache keys → disk/CPU DoS

quality and max_dimension come from the remote peer as arbitrary ints with no range validation and flow into the cache filename used by _get_converted_media_path. Any peer holding a file-access grant (granted just by loading one page) can request the same image with thousands of distinct values; each unique combo spawns a conversion subprocess (10 s timeout, serialized on the media lock) and writes a permanent cache file with no eviction — a straightforward remote disk-fill and CPU-exhaustion vector. Clamp quality (e.g. 1–100) and max_dimension to a sane maximum and bound/evict the cache.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

"--rrc-hub",
rrc_cfg["hub"],
"--rrc-rooms",
",".join(rrc_cfg["rooms"]),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: RRC room names containing commas round-trip lossily through CLI args

normalize_rrc_bot_config accepts room names up to 64 chars without rejecting commas, but rooms are serialized as ",".join(rrc_cfg["rooms"]) here and re-split on "," in bot_process.py — a configured room "lobby,secret" silently becomes two rooms and the stored config diverges from the running bot. Reject commas in the room-name validation loop in normalize_rrc_bot_config.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

} else if (url.startsWith(":")) {
path = url.slice(1);
}
if (path.startsWith("media/")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Hash-qualified and :/-relative image URLs are never accepted

extractMicronImageFilePath splits on ":/" (line ~785), so for hash:/media/x.png and :/media/x.png the resulting path is /media/x.png (leading slash). The checks here only accept path.startsWith("media/") / "file/", so both documented forms (the comment above says "Accept hash:/media/... ... and relative :/media/...") return null; only a bare media/x.png — a shape Nomad URLs never produce — parses. The common nomadnetwork://<hash>:/media/... image URL never renders an image placeholder. Strip the leading / after splitting (or accept both forms in the prefix checks).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

out = out.replace(EXPLICIT_RE, (match, body) => geoAnchor(body.trim(), body.trim()));

// Maidenhead locators, 6+ chars only for auto-linking to avoid noise.
out = out.replace(MAIDENHEAD_RE, (match) => geoAnchor(match, match));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Later passes re-match earlier passes' generated markup

linkifyGeoRefs runs EXPLICIT → MAIDENHEAD → LATLON sequentially over the output of the previous pass. An explicit link like grid:FN31pr first becomes <a ... data-geo-text="FN31pr">FN31pr</a>, then the MAIDENHEAD pass matches FN31pr inside the attribute value and the anchor text, injecting a full anchor template into the attribute (breaking it). Same for geo:51.5, -0.1 with the LATLON pass. The result is nested anchors and a corrupted data-geo-text attribute rendered into @html in relay chat. Protect/placeholder earlier anchors (as LinkUtils.protectAnchors does) before subsequent passes.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

* Ported and simplified from meshchatx/src/backend/markdown_renderer.py
*/
static render(text) {
return cachedRender(text, MarkdownRenderer._renderUncached);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Shared render cache across render() and renderBasic()

Both pipelines call cachedRender with the same module-level renderCache keyed only by raw text. The same string rendered by both (e.g. posted to an LXMF conversation and an RRC room) returns whichever pipeline cached first — headers/paragraphs vs. inline-only markup. The output also depends on GlobalState.config (via LinkUtils.renderAllLinksdefaultNomadPagePath()), so cached link hrefs go stale after a config/identity change despite the "pure function of the input" comment. Key the cache per pipeline (and per relevant config) or only cache within _renderUncached.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

const info = readImage(container);
const index = container.getAttribute("data-mu-image-index");
post({
type: "image-action",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: "Load image" button posts a message with no handler — dead control

The frame posts { type: "image-action", ... }, and the frame itself handles a set-image reply (line ~465), but nothing outside the frame ever sends set-image (the parent bridge handles only ready/pong/render-*/shell-background/aborted/navigate, and the new images field on render-done is dropped by the bridge). Clicking "Load image" is inert end-to-end. Add the parent-side wiring or gate the placeholder until it exists.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

for (const key of NUMERIC_KEYS) {
const value = String(draft[key] ?? "").trim();
if (value) {
patch[key] = Number(value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Non-numeric input silently becomes null

patch[key] = Number(value) has no NaN guard: typing abc in e.g. cooldown yields NaN, which JSON.stringify serializes as null and the backend treats as "clear the override" — garbage input silently deletes the bot's setting instead of erroring. Guard with Number.isFinite.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Ivan added 16 commits September 13, 2026 00:03
Apply master's route behavior across the split route packages: bounded
JSON reads, canonical HTTP error helpers, streamed multipart uploads via
write_field_to_path, blocking work moved off the event loop, and input
validation for map export, path probe, stickers, and telephone audio.

Add master endpoints GET /api/v1/identity/{identity_hash}/lxmf-address,
GET /api/v1/rrc/search, GET /api/v1/rrc/servers/{hub_id}/stats, and the
nomadnet archives export route, and regenerate the HTTP route fixture.

Align nomad ws download handlers with master's started-before-schedule
ordering and private-download archive skip, and repoint stale flat-module
patch targets in interface discovery and nomad ws order tests to the
live-name resolution point.
Replace the inline start modal flow with master's dedicated setup page at
/bots/new so template selection, lxmf config, icon, custom commands, and
rrc options are configured before starting. Add BotSetupPage with
BotCustomCommandsEditor and BotRrcFields, draft builders in botDrafts,
and icon/custom/rrc fields on the bot api payload and record types.

Fix the stale BotsPage chat test which queried the i18n key while the
registered fallback returns the translated title.
Convert master's RelayBotsPage and RelaySearchPage to Svelte 5 and wire
them into RelayChatPage's bots and search tabs, including the shared
SearchInput component and the rrc bot record and known-hub types.
Convert master's MapTabContextMenu to Svelte 5 and wire rename, publish,
and remove actions into MapBrowser and MapTabBar.
Convert master's PublishSiteModal to Svelte 5 with the micronPublish
helper and wire it into MicronEditorPage and the publish dropdown.
Move ColourPickerDropdown from the profile feature into ui/svelte so it
can back the bot icon editor, extract reusable hex helpers into
colorUtils, and update the profile icon page and module ownership test.
Annotate micron parser helpers whose null defaults collapsed to narrow
literal types, type the indexeddb void promises, narrow optional geo
coordinates before use, declare the sandbox card abi field, and pass an
undefined-safe id to the bits-ui switch root.
The flat-module-to-package split dropped the helper from the public
surface that master exposes and test_rrc_bot imports.
The privacy toggle renders as a switch role button after the bits-ui
migration so .checked no longer exists on the element.
Migrate window and document listeners, matchMedia change handlers, and
stable element scroll listeners to runed useEventListener across pages
and shared components. MediaQueryList targets stay in state so the
effect rebinds once assigned on mount.

Kept the transient pointermove and pointerup pane-resize listeners in
MessagesPage since always-on binding would persist panes on every
pointerup.
Restore master's per-handler try/catch in dispatchWsEvent so one faulty
listener cannot starve later ones, and render the landlock kernel ABI
chip on about security cards like the Vue version did.
Drive demo prefs and identity readiness through GlobalState now that the
pinia stores are gone, rewrite the SettingsNav and ToolsPage cases that
still called Vue mount helpers, switch settings section assertions to
the switch role semantics of the bits-ui toggle, and raise the relay and
nomad page regression caps and leaf allowlist for the ported views.
Bring next to dev parity outside the frontend: AppContainer window
object grants with init-failure fallback, the AppContainer child-launch
self-check probe, backend manifest verification in packaging and CI,
Android startup failure surfacing, the rngit release helper, rns 1.5.4
and lxst 0.5.3 with refreshed license data, and the 4.9.1 version bump
with its changelog entries.
"""Download the filtered archive set as a single zip bundle."""
query = request.query.get("q", "").strip()
destination_hash = request.query.get("destination_hash", "").strip() or None
rows = app.database.misc.get_archived_pages_paginated(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Missing require_database guard and exception handling

Every sibling archives route (e.g. pages.py) calls require_database(app) before touching app.database and wraps DB calls with http_for_database_exception. Here, if app.database is None during an identity switch, app.database.misc raises AttributeError → unstructured 500 instead of a 503; a retryable sqlite error also surfaces as a raw 500 instead of the sanitizing 503 convention.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

}
});

worker.addEventListener("error", (err) => this.onerror(err as unknown as Error));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Worker failure never rejects pending calls — translation hangs forever

The error listener only forwards to this.onerror; the pending entries in the pending map are never rejected. If the vendored worker fails to load or WASM instantiation fails, await call("initialize", ...) on line 115 never settles, so TranslationService.translate never resolves and the Translate button in TranslatorPage.svelte spins with isTranslating stuck true. There is also no timeout. Reject all pending (and future-failing) calls on error/messageerror events.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

* Default publish filename for a tab: sanitized tab name, keeping an existing
* allowed extension or appending .mu.
*/
export function defaultPublishFilename(tab: MicronTab, index: number): string {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: publishSite flow has no filename-collision check — pages silently overwrite each other

sanitizePublishFilename collapses distinct tab names to the same filename (e.g. "My: Page" and "My Page" both → My_Page.mu; home.mu vs Home.mu). The single-page flow dedupes against existing pages via resolvePublishPageBase, but the site flow (MicronEditorPage.publishSite) posts each entry with no intra-batch or server-side collision check: later pages overwrite earlier ones, published still counts them as successes, and the success toast reports published/total as if all pages were distinct.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

window.location.hash = `#/bots/new?template=${encodeURIComponent(template.id)}`;
}

async function handleStartBot(bot: BotRecord): Promise<void> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: handleStartBot never sets actionInProgress

Unlike every sibling handler (handleStopBot/handleRestartBot/handleDeleteBot), start does not set/clear actionInProgress, and BotCard receives it to disable buttons. The Start button stays enabled while the request is in flight, so a double-click fires the start endpoint twice (restart semantics — the second call kills and restarts the fresh process).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

const hubObj = hubs.find((h) => h.hub_hash === target.hubHash);
if (hubObj) {
selectRoom(hubObj, { name: target.room });
} else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Search-result fallback navigates to a room that can never render

When the hit's hub_hash is not in the connected hubs list, this branch sets selectedHubHash/selectedRoomName and loads messages, but selectedRoom is derived from hubs.find(...) (line 100) and stays null — and the chat pane is max-md:hidden with no header when selectedRoom is null (lines 592-596). The user is switched to the chat tab with an empty pane and no feedback. Hits for removed/disconnected hubs silently do nothing.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

}
</script>

<Dialog.Root bind:open onOpenChange={handleOpenChange}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Focus restoration lost in the bits-ui rewrite

The old implementation saved document.activeElement on open and re-focused it on destroy (the removed lines here). The rewrite delegates to bits-ui, but this modal is opened programmatically with no Dialog.Trigger in the tree, so bits-ui has nothing to restore focus to and focus falls to <body> on close — a keyboard/AT regression (WCAG 2.4.3) versus the pre-change behavior. Consider capturing the previously focused element and restoring it on close.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

});
lastSavedName = (response.data as { name?: string })?.name || page.name;
published++;
} catch {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Per-page publish failures are swallowed to console

A site where most pages fail to upload still closes the modal and shows a success toast (2/10 pages to {server}) plus the "open in NomadNet" confirm; the failures are only visible in the console. Surface per-page or aggregate failure state to the user.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Ivan added 5 commits September 13, 2026 01:21
…rgets

Star imports drop underscore names, so route slices that referenced
private helpers raised NameError at request time. Point the auth
security handlers at the public is_loopback_bind_host from path_utils
(matching master), fix the live-name entry that resolved a nonexistent
meshchat attribute, rename the sticker document size cap to a public
constant, and import list_host_network_interfaces directly in the
interfaces crud slice. Re-export PluginSecurityError and
MINIMAL_PLUGIN_WAT from the plugin_manager package, and repoint tests
that patched pre-split module paths.
run_self_test lost the umsgpack, LXST telephony, audio codec, miniaudio,
and translation pack checks when it moved out of meshchat.py, so the
self-test response no longer matched the JSON contract. Add env_set,
env_unset, and env_restore helpers so bot lifecycle and the
AppContainer launch probe stop reading and writing os.environ directly,
and pass the probe marker through argv for the -c path. Add the missing
appcontainer_launch property to the self-test schema.
Port the master Nomad image-loading policy to the Svelte nomad page:
per-node image policies, cached image downloads with relative and
absolute URL resolution, and renderer metadata for image actions.

From dev, port the crash tab iframe teleport to document.body with a
rect-sync loop, transition-aware geometry filtering, and parked frame
visibility gating. Preserve the browse list context so navigation from
a list keeps the list tab instead of closing it, restore the last
sidebar tab on remount, and add the mobile URL row.
From dev, port the conversation soft-resync merge that restashes the
fresh first page and merges newer messages without clobbering scroll,
the prefetch drop on delivery events, relay room back-navigation that
restores the prior view, the hidden overflow tabs on narrow widths,
and the announce interval input that accepts human durations such as
hours and days across all locales.
RelayChatPage and ConversationViewer grew past their regression caps
after the dev resync and relay view ports. Raise both caps and run
prettier over three committed files that were not formatted.

p_sd = ctypes.c_void_p()
p_dacl = ctypes.c_void_p()
get_status = user32.GetSecurityInfo(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: GetSecurityInfo / SetSecurityInfo are bound to user32, but these Win32 APIs are exported by advapi32.dlluser32 has no such exports, so user32.GetSecurityInfo raises AttributeError: function 'GetSecurityInfo' not found the first time grant_winstation_desktop_access runs on Windows.

The sibling _set_path_access correctly loads GetNamedSecurityInfoW from advapi32 (line 569), so this looks like a copy mistake. The caller at line 1170 only catches OSError, so the AttributeError propagates out of launch_backend_sandboxed and crashes the launcher before CreateProcessW — every MESHCHAT_APPCONTAINER auto/forced launch on Windows would fail. Same defect applies to user32.SetSecurityInfo at line 692. The unit tests miss it because they monkeypatch grant_winstation_desktop_access entirely.

Fix: load both functions from advapi32 (GetProcessWindowStation, OpenWindowStationW, GetThreadDesktop, OpenDesktopW, etc. legitimately live in user32; the usages at 722+ are fine).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

"import sys; open(sys.argv[1], 'w').write('ok')",
marker,
]
result = ac.launch_backend_sandboxed(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: check_appcontainer_launch is the only self-check with no try/except Exception around its launch/verify body — every sibling check (e.g. check_public_assets, check_translation_pack_import) catches Exception and returns _status(False, ...).

Here ac.launch_backend_sandboxed(...) at line 399 runs bare with only a finally: env_restore. Any non-OSError exception (concretely, the user32.GetSecurityInfo AttributeError in appcontainer_sandbox/core.py:663, or any unexpected error in the marker-read block at 410-426) propagates out of the check and aborts the whole run_self_test, killing all remaining rows instead of reporting one failed "AppContainer Launch" row.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

return false;
}
const downloadId = json.download_id as number | string | undefined;
if (body!.status === "chunk") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: chunk/started events are claimed based only on this.callbacks[imageId] presence, with no ownership check on those statuses (ownsNomadImageDownloadEvent runs only for the non-chunk paths).

When cancelStaleDownloads() (called from onImages/clear() during quick navigation) deletes the callback for an in-flight image, the backend's subsequent nomadnet.file.download events for that cancelled image fall through (handleFileDownloadEvent returns false) into the generic handler onNomadFileDownloadEvent in nomadPageDownloadEvents.ts. There, with currentFileDownloadId == null, chunk events set isDownloadingNodeFile: true + progress UI, and a late success would save the image bytes as a phantom user download; a late failure leaves the file-download state stuck since loadPage() does not reset it. A request-id (or download-id) check on the chunk/started branch would close the hole.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

this.deps.setImage(imageId, "loaded", { dataUrl, actualSize });
}

private onDownloadFailure(imageId: number, reason: unknown): void {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Buffered chunks are never freed on failure or cancel. appendDownloadChunk (line 379) stores partial base64 image bytes in the shared page-level nomadFileDownloadChunkBuffers, but onDownloadFailure (here), cancelStaleDownloads() (line 436) and clear() (line 452) never call discardDownloadChunks — only a chunked success consumes them.

Compare the generic handler's failure path in nomadPageDownloadEvents.ts:257-259, which explicitly discards. Every failed or cancelled image download permanently leaks its buffered bytes in the page's chunk-buffer map until component teardown.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

}
const cached = this.cache.get(nomadImageCacheKey(destinationHash, filePath));
if (cached) {
this.deps.setImage(index, "loaded", { dataUrl: cached.dataUrl, actualSize: cached.size });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: A success with empty/absent file_bytes yields getNomadImageDataUrl()null, which is cached as {dataUrl: null, size: "0 B"} in onDownloadSuccess (lines 419-422). Subsequent load() calls hit this cache (lines 343-345) and report "loaded" with dataUrl: null — the placeholder claims success and shows nothing, with no error surface. Consider treating an empty payload as a failure instead of caching the null result.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

? 'bg-sem-canvas text-sem-fg font-semibold'
: 'text-sem-fg-muted hover:bg-sem-surface/80'}"
onclick={() => {
overflowMenuOpen = !overflowMenuOpen;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: overflowMenuOpen has no outside-click or Escape dismissal, and pinned-tab clicks (the onclick on the tab buttons above only sets view) do not close it. On phones the dropdown overlay stays open covering the chat pane until the user re-taps the overflow button. A svelte:window keydown/outside-click handler or closing it on any tab onclick would fix it.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

const s = String(text ?? "")
.trim()
.toLowerCase()
.replace(",", ".");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: .replace(",", ".") runs before the bare-integer fast path, so a thousands-separated value like "1,440" becomes "1.440" and parses to Math.round(1.44) = 1 minute — the user's intended 24 h interval silently collapses to 1 min. Decimal-comma support and thousands-separator input collide; consider rejecting bare numbers containing both a comma and a grouped digit triad, or only applying comma→dot when a unit suffix is present.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread scripts/rngit_release.py
fail(f"artifacts directory {out} does not exist; run build first")
if not any(p.is_file() for p in out.iterdir()):
fail(f"artifacts directory {out} is empty")
clean_generated_sidecars(out)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: cmd_create calls clean_generated_sidecars(out) before resolve_notes(args, tag); if the user forgot notes in non-interactive mode, resolve_notes calls fail() — but the artifacts directory has already had its manifest.rsm/*.rsg sidecars permanently deleted. Cleaning after notes resolution (or only on rngit success) would avoid stripping signatures on a validation failure.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread scripts/rngit_release.py

def write_editor_shim(notes_text: str) -> Path:
"""Create a temp editor that replaces rngit's notes template with our text."""
tmpdir = Path(tempfile.mkdtemp(prefix="rngit-release-"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: write_editor_shim allocates tempfile.mkdtemp(prefix="rngit-release-") and never cleans it up; every notes-bearing create/release run leaks a temp directory containing notes.md and an executable shim. Consider a try/finally with shutil.rmtree(tmpdir) after run_rngit consumes the shim.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

issues.push(`Missing: ${relPath}`);
continue;
}
if (sha256(fullPath) !== expectedHash) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: sha256(fullPath) calls readFileSync unguarded; if a manifest entry collides with a directory in the packaged tree (or the file is unreadable), the script dies with an uncaught EISDIR/EACCES stack trace instead of reporting a clean Modified:/Unreadable: issue and exiting 1, weakening the CI signal this script exists to provide. A try/catch that pushes an issue would keep the failure actionable.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Ivan added 4 commits September 13, 2026 01:54
Clamp remote-controlled quality and max_dimension before they reach
media cache keys so peers cannot spawn unbounded conversions, and wrap
the quality int cast like its siblings. Reject commas in RRC room names
so CLI serialization round-trips. Add the require_database guard and
database exception mapping to the archives export route, and pass the
identity hash rather than the identity object to RRCServerManager in
the shared fixture so owner auto-trust matches production.
Stop microphone recording and revoke preview URLs when the composer
unmounts mid-session, reset virtual-list height measurements when the
conversation changes and cap the record size, scope relay discovery to
the rrc.hub aspect and warn instead of opening a blank pane for
disconnected hubs, re-apply nomad route targets when keep-alive pushes
new route props, accept leading-slash micron image URLs, protect
generated anchors from later geo-linkify passes, key the markdown
render cache per pipeline and clear it on config change, reject pending
bergamot calls on worker failure, dedupe publish-site filenames and
surface per-page failures, disable the bot start button while a start
is in flight, and restore focus to the invoker when programmatic modals
close.
Replace the O(n) visible-row scan in both virtual message lists with a
binary search over the sorted layout, defer loading the wasm-backed
translation service until translation is actually used, and skip
conversation and folder poll ticks while the previous request is still
in flight.
Annotate the announce interval parser regexes with the bounded-input
justification used elsewhere, run prettier over the generated license
manifests and fixtures, and allowlist the nomad browser host leaf that
grew past its cap after the image policy and route-restore ports.

// Link rendering reads live GlobalState config (for example the default nomad
// page path), so cached output must be dropped when GlobalState mutates.
subscribeGlobalState(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Render LRU is wiped by every high-frequency GlobalState mutation

subscribeGlobalState fires for the whole state object, not just config: unreadConversationsCount is reassigned on every conversation poll, alongside relayChatUnreadCount, missedCallsCount, activeCallTab, networkDegraded, and so on. Each of those mutations now clears the entire 800-entry render cache — defeating the memoization exactly during busy chat, which is what this cache exists for. The nomad_default_page_path dependency only needs invalidation on config changes; clear the cache only when a config key mutates (e.g. in the mergeGlobalConfig path) instead of on any state write.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

try:
quality = int(quality)
except (TypeError, ValueError):
quality = MEDIA_QUALITY

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: OverflowError escapes the new quality clamp

int(quality) catches only (TypeError, ValueError), but Python's JSON parser accepts Infinity/NaN literals, so a remote peer sending "quality": Infinity makes int(float('inf')) raise OverflowError — escaping this clamp and the responder's parse at line 1048, which has the same except tuple. Since this clamp was added precisely to sanitize untrusted peer input, add OverflowError to both except tuples (note int(float('nan')) already lands in the covered ValueError path).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

});
if (!session) return;
if (destroyed) {
// Unmounted while getUserMedia was still resolving; release the mic.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Preview object URL leaks in the unmount-mid-start path

stopAudioRecordingSession(session) here discards the resolved ComposeAudio, whose audio_preview_url was just created with URL.createObjectURL — it is never revoked. The destroyed branch at line 193 handles this correctly; mirror it, e.g. .then((r) => r?.audio_preview_url && URL.revokeObjectURL(r.audio_preview_url)), so the mic-release path doesn't leak the blob URL (and skip codec2's decode work too).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

for (const entry of entries) {
entry.reject(error);
}
this.onerror(error);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Calls after a fatal worker error hang again, and onerror may be undefined

Two edges in the new failure path: (1) failPending drains only the in-flight batch — after the worker dies once, the exports proxy stays live and every later call() posts into a dead worker and creates new pending entries that no further error event will ever reject (a worker fires error typically once). Consider a failed latch that rejects future calls immediately. (2) this.onerror(error) is invoked unguarded; TranslationService constructs new BergamotBacking() without an onerror option, so if the vendor base class doesn't default it, this throws inside the listener. this.onerror?.(error) is cheap insurance.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

// it back on close for WCAG 2.4.3 focus order.
let previouslyFocused: HTMLElement | null = null;

$effect.pre(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Focus restore never runs when an open modal is unmounted

Capture and restore both depend on open transitioning while this component is alive. When a parent unmounts the Modal while open (keepAlive page teardowns do exactly this), the restore $effect is torn down rather than re-run with open=false, so previouslyFocused is dropped and focus falls to <body> — the same WCAG 2.4.3 gap this change set out to close. Register the restore inside the effect's cleanup ($effect(() => { if (!open) return; ...; return () => { if (!open) el?.focus?.(); } }) or equivalent) so destroy-while-open also restores.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

if (!hubObj) {
// The hub is not in the local list (never added or disconnected),
// so selecting it would leave the pane blank with no feedback.
ToastUtils.warning(t("relay_chat.hub_not_added"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Misleading "not added" toast when the hubs list is merely stale

openSearchResult now hard-gates on the client-side hubs array. If fetchHubs() hasn't resolved yet (or its catch emptied the list), clicking a search result for a genuinely connected hub shows "Add this hub to your list before opening a room" and silently does nothing. Search hits originate from the same server-side hub set, so a refetch-before-give-up (or falling back to the previous direct-open path) would avoid blaming the user for a transient empty list.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants