rmnr
DocsTourSlicesBest PracticeAgentsInstall

Command Palette

Search for a command to run...

New
  • Changelog
Release notes

Changelog

What changed in each rr release — page 2 of 17. Full prose history in CHANGELOG.md.

JUN282026
Improvementsite@perf-opt-sweepJun 28, 2026

Performance pass — lazy-load highlight.js in notion, index the audit-log-by-action query (rest already optimal)

Targeted perf sweep with the verifier inverted to REJECT premature/speculative optimization (no speculative memo, no 'could be faster' rewrites). The scout already showed the codebase follows its own rules — 0 bare .collect() in convex, 0 internal raw <a>, KaTeX already lazy — so only 2 concrete, structural wins survived verification; the other categories came back clean and were intentionally left alone. (1) notion-shell CodeBlock.tsx statically imported `highlight.js/lib/common` (~35 languages, ~100kB) at module top, so the highlighter JS shipped in the notion editor's initial bundle for EVERY page — including the majority with no code block. Now it loads lazily via dynamic import on the first code block, cached at module scope; until it resolves the block renders the same HTML-escaped plaintext it already used as the fallback, so SSR/first-paint output is unchanged (no hydration mismatch) and highlighting fills in client-side. The small CSS stays static. (2) convex/_shared/auditLogger_queries.ts getAuditLogsByAction did `.filter(workspaceId AND action)` over the UNBOUNDED activityEvents audit table — a full-table scan that grows with every event ever logged. Switched to `.withIndex("by_workspace", …)` (the same index the sibling getAuditLogsByWorkspace already uses one function up) and kept action as a post-index .filter — same most-recent-first result, but the scan is now scoped to the workspace partition. No schema migration (index already exists), no new dependency. Deliberately NOT changed (verified already-optimal, not worth touching): react-konva is core to the image-editor and only mounts when the editor is open; the ~5 raw <img> are all blob/object-URL/runtime sources where next/image does not apply; KaTeX is already lazy; no hot-render recompute worth memoizing. tsc + slices:check (70 ok) green.

Performance

  • notion-shell/CodeBlock — highlight.js (~100kB) now lazy-loads on the first code block instead of shipping in every notion page's bundle
  • convex/auditLogger — getAuditLogsByAction scoped to the by_workspace index instead of full-scanning the unbounded activityEvents table

Audited clean (left as-is)

  • image-editor konva (core, mounts only when editor opens), raw <img> (all blob/runtime sources), KaTeX (already lazy), hot-render recompute (none worth memoizing)
JUN282026
Fixsite@nonslice-defect-sweepJun 28, 2026

Defect sweep of the non-slice surfaces — packages/mcp, packages/cli, app server (convex copy-source came back clean)

30-unit multi-agent sweep (21 convex copy-source dirs + 5 cli units + 3 mcp units + the app server) — each unit found-then-adversarially-verified (default reject, fix must be minimal + gate-respecting). All 21 convex/features dirs came back CLEAN: last session's IDOR/null-throw fixes plus the new audit-convex-api-paths gate already locked that gate-invisible surface down. 7 real defects fixed elsewhere (6 verified-as-proposed + 1 whose proposed fix was wrong and got reworked from the verifier's corrected patch): (P1) MCP POST /api/oauth/token with a JSON body of literal `null` passed the SyntaxError-only try/catch then destructured null → unhandled TypeError that rejects before the per-request try in http-transport → unauth one-request server crash; fixed with `parsed || {}`. (P1) MCP /oauth/authorize redirect_uri with percent-encoded CR/LF passed validation because the WHATWG URL parser silently strips control chars, but the raw string kept its \r\n and blew up res.setHeader('Location', …) with ERR_INVALID_CHAR on the POST branch → unauth DoS; fixed by rejecting control chars (/[\x00-\x1f\x7f]/) before new URL(). (P2 ×2) MCP composeInit/composeAdd emitted request-controlled template/features/skills verbatim into the npx command strings the operator copy-pastes into a terminal — shell injection; now routed through the same sanitizeShellArg the sibling composeApp already used. (P2) CLI readStateFromRr lacked the Array.isArray guard its three sibling fields have, so a hand-edited rr.json with a non-array slices/features crashed `rr add` preflight + `rr compose` instead of the documented empty-state fallback. (P2) CLI listAllDNA had no try/catch around readDNA, so one corrupt or non-kebab-named .dna.json crashed all of `rr graph` / buildLineageGraph / the MCP lineage resources instead of being skipped. (P2) app/api/agent-stream returned a raw 500 on a malformed tool-role message (m.results undefined → .map throw); now guarded with Array.isArray. Each fix has a runnable behavioral check; tsc + slices:check green. CLI 1.14.2 + MCP 1.2.5.

JUN282026
Improvementslices@ponytail-sweepJun 28, 2026

Ponytail dead-code sweep — the 37 slices the earlier dedup batches hadn't touched

Closed the gap left by the two prior dedup passes (batch-1 −636 across 20 slices, dead-code −217 across 14): 8 parallel scanners audited the remaining 37 slices for genuine within-slice over-engineering — dead internal symbols, unused exports not on the public entry, redundant wrappers, one-impl abstractions, speculative scaffolding. Copy-first discipline held: NO cross-slice dedup, and any export re-exported from a slice's public index/config (the product a consumer copies) was kept even when unused in-repo. Result: 32 slices already minimal (incl. notion's 10k-LOC tree, app-store, all 17 small slices), 5 had real cuts. Every cut grep-verified before applying — one finding was REJECTED on verification: browser's isUrlLike/normalizeUrl were flagged dead but toTarget() calls both (the agent misread 'called by' as 'replaced by'), so browser stayed untouched. Cuts: appshell chromeInsets() dead getter (setChromeInsets stays); reel-editor isXfadeSource() + tracksByKind() unused composition helpers; file-explorer mediaKind()+MediaKind type — a speculative media-viewer ext-mapper with zero callers (the VIDEO/AUDIO sets it used stay, they back the icon/color/route fns); admin 4 dead barrels (a types/index.ts re-export duplicating the public index, plus 3 empty `export {}` scaffolds) deleted, now-empty dirs removed. ~31 LOC + 4 files. tsc + slices:check (70 ok) green.

JUN282026
Improvementsite@convex-api-path-gateJun 28, 2026

New CI gate — convex internal/api references must resolve (locks in the runtime-null-throw class)

Regression gate for the gate-invisible bug class found this session. convex/** is excluded from the root tsc, and audit-convex-features bans the plural FILE name (mutations.ts) but not a plural REFERENCE — so `internal.features.ai.mutations.logUsage` (plural) against the singular mutation.ts file resolved to undefined in the generated api object and threw 'Cannot read properties of undefined' at runtime, invisible to every gate. That shipped 3× (ai, bookings, newsletter) before being fixed. New audit-convex-api-paths.mjs walks convex/ and asserts every `internal.features.<path>.<fn>` / `api.features.<path>.<fn>` reference resolves to an existing convex/features/<path>.ts that exports <fn> — catching plural-vs-singular, wrong module/dir names, and renamed/removed exports. It skips the consumer-side `.slices.*` namespace (resolves only after consumer codegen, per Hard Rule 6). Wired into slices:check (so it runs in pre-commit + pre-push). Verified: passes clean on the current tree, and fails with the exact diagnostic when the historical `ai.mutations.logUsage` ref is injected.

Tooling

JUN282026
Fixsite@app-server-huntJun 28, 2026

Live-site server surface — open redirect in proxy.ts closed; build-chat 500 on malformed input fixed

Security + correctness sweep of the deployed app's server surface (admin auth, the two LLM endpoints, external proxies, proxy.ts host rewriting, server actions), each finding adversarially verified — a speculative unsplash length-cap was correctly rejected (already rate-limited, hardcoded upstream host, percent-encoded query). 2 real bugs fixed: (1) SECURITY — proxy.ts built the external-demo redirect via new URL(request.nextUrl.pathname + search, external), putting the attacker-controlled path as the URL INPUT rather than mutating the trusted base. Empirically verified against the repo's Next 16.2.6: a request to a valid demo subdomain with path //evil.com/x yields nextUrl.pathname === '//evil.com/x' (Next does not normalize the leading //), and new URL('//evil.com/x', vercelOrigin) resolves to https://evil.com — a 307 OPEN REDIRECT off the allowlisted origin to any host (phishing / OAuth-token-leak). Now the dest is built from the trusted base first, then pathname/search assigned, so '//evil.com' collapses onto the allowlisted origin (verified: crafted // and backslash paths all stay on the Vercel host; normal paths preserved). (2) build-chat route did (body.messages ?? []).filter(...) before its try/catch — a non-array messages ({"messages":"x"}) passes the ?? guard but throws TypeError on .filter, returning a raw 500 instead of the clean 400 the surrounding validation produces; replaced with Array.isArray(...), matching the sibling agent-stream route. tsc + slices:check green.

JUN282026
Fixslices@async-error-huntJun 28, 2026

Async error handling — 6 unhandled rejections / silent failures + blob leaks fixed (image-editor, notion-shell)

Swept slice components for unhandled errors on user-triggered async paths + floating promises, each adversarially verified (0 rejected). 6 real bugs fixed, all P3 but reachable: image-editor menu-bar + top-bar openImage/openFile awaited loadImage() with no try/catch — an undecodable picked image (corrupt, truncated, HEIC) rejected with an unhandled promise, no layer added, no feedback, AND the createObjectURL blob leaked; now wrapped with a catch that revokes the blob. image-editor removeBg() (both bars) called removeImageBackground() — which dynamically downloads an ONNX model + WASM on first run — with try/finally but NO catch, so an offline/unsupported first use escaped as an unhandled rejection (busy reset, but the error was lost with zero feedback); added the catch. notion-shell CodeBlock copy + NotionBlock copy-link awaited navigator.clipboard.writeText with no guard — rejects in an insecure context / when the document isn't focused / on denied permission, leaving an unhandled rejection and (for CodeBlock) the copied state never set; both guarded. These slices have no error-surface channel, so the fixes prevent the unhandled rejection / stuck state / blob leak (gate-permitted swallow). slices:check 70 ok + tsc green.

Fixes

JUN282026
Fixcli@1.14.1 · mcp@1.2.4Jun 28, 2026

CLI + MCP defect hunt — reflected XSS in the MCP OAuth consent page closed; CLI boolean-flag parsing fixed

First defect sweep of the published packages (packages/cli + packages/mcp — bugs here ship to every npx rr / MCP user). 10 module groups audited, every finding adversarially verified; 8 candidates surfaced, 6 correctly rejected as unreachable/dev-only (dead removeSkill, conflict-gated compose accepted-set, curated-archive dna, quote-free sync-skills regex, gen-manifest write-ordering, self-inflicted init path). 2 real bugs fixed: (1) SECURITY — the MCP OAuth 2.1 consent page rendered the attacker-influenced redirect_uri inside an onclick JS-string sink (window.location='…'); its esc() HTML-encodes the apostrophe to &#39;, which the HTML parser decodes back to a literal ' before the JS runs, so a redirect_uri like https://localhost/'-alert(document.domain)-' (valid https URL, passes the protocol check) breaks out and executes arbitrary JS on the MCP origin when the victim clicks Deny — reflected XSS. The Deny button is now an <a href> (navigation context where HTML-escaping is correct), styled to match. (2) CLI — parseFlags consumed the next token as a value for EVERY --flag (no boolean notion), so `rr add my-slice --force ./apps/web` swallowed ./apps/web as the value of --force and silently installed into the cwd instead. Replaced with a value-flag allowlist (target/template/category/at/skills/features for cli.js; from/to/repo-root for the duplicate parser in migrate-load.mjs) so boolean flags before a positional no longer eat it. Verified: XSS render check + parseFlags behavioral check + CLI vitest 57 pass. CLI 1.14.0→1.14.1, MCP 1.2.3→1.2.4 (publish pending OTP).

JUN282026
Fixslices@a11y-depth-2Jun 28, 2026

a11y depth pass 2 — 22 fixes: form controls labelled + click-only tabs/rows made keyboard-accessible

Second deep a11y sweep (form controls without labels + click handlers on non-interactive elements), each finding adversarially verified, 0 rejected. FORM LABELS: 15 inputs/textareas that exposed only a placeholder (not an accessible name) got aria-labels — landing-sections newsletter, vector-search, data-table filter, comments reply, assistant composer + the agent/skill/automation Field forms, browser omnibar, ai-chat + ai-router chat fabs, user-management members search, reel-editor + image-editor AI panels, image-editor properties panel (11 transform/stroke/brush fields whose visible <Label> had no htmlFor), app-store create form, file-explorer rename. KEYBOARD ACCESS: controls that were click-only (no role/tabIndex/onKeyDown) and thus unusable by keyboard or screen-reader users got proper key handling — code-editor + browser tab strips (role=tab + Enter/Space → select), media-studio + image-editor layer rows (role=button + guarded Enter/Space → select), and file-explorer's list-view row was converted from a bare <div> to a <Button> mirroring its own grid branch (native keyboard access, layout unchanged). One control skipped: skill-form's "Allowed tools" wraps a custom ToolPicker (not a labelable input). slices:check 70 ok + tsc green.

Keyboard access (was mouse-only)

JUN282026
Fixslices@defect-hunt-r3Jun 28, 2026

Call-graph + a11y depth — aiChat dead nonexistent-module ref removed, 6 more icon-button/dialog-title a11y misses

Two more verified passes. CALL-GRAPH (convex/** excluded from root tsc, so gate-invisible): swept every ctx.run*/scheduler call against its target validator. ai-chat chat action called api.aiConfig.get — a module that exists NOWHERE in the tree (a leftover from the source project's agencyAiConfig singleton, referenced only in that one file); on the ANTHROPIC_API_KEY-set path (the real use case, outside the try) it threw 'Could not find public function'. Removed the dead runQuery + now-unused api import, inlined the existing default system prompt. (Noted but deliberately NOT changed: seo slice's generate action references api.slices.auth.me / api.slices.seo.* — the whole action targets the consumer-side `slices.*` codegen namespace, not rr's `features.*`, so it's a consumer-composition contract, not an rr-repo bug.) A11Y depth (round 1 only scratched this, capped at 10): 5 more icon-only controls with no accessible name got aria-labels — code-editor tab close (dynamic label w/ unsaved state), assistant glyph picker (matching its sibling ColorPick), appshell window-overview reveal + dock app links, notion editor toggle block; and icon-picker's dialog-fallback DialogContent had no DialogTitle (Radix a11y violation + runtime warning) — added an sr-only title. slices:check 70 ok + tsc green.

Runtime correctness

JUN282026
Fixslices@defect-hunt-r2Jun 28, 2026

Convex security sweep R2 — 3 runtime null-throws (wrong internal API path), tenant priv-esc + owner-write IDOR, private-row leak

Per-feature-dir security sweep across all 20 convex/features handlers, each finding adversarially verified. CORRECTNESS (these crashed at runtime and shipped broken to every consumer — convex/** is excluded from root tsc so neither typecheck nor the import-gate caught them): ai callModel, bookings calWebhook, and the whole newsletter broadcast fanout referenced internal.features.<x>.MUTATIONS/QUERIES.fn (plural) but every module file is mutation.ts / query.ts (singular), so the generated API node was undefined and each ctx.runMutation/runQuery threw 'Cannot read properties of undefined' — for ai, AFTER the paid generateText() spend; for bookings, on every Cal.com webhook; for newsletter, before a single email sent. Fixed plural→singular (7 refs) + the same typo in a testimonials operator doc-comment. SECURITY: user-management hierarchy linkTenant gated members.manage only on the parent tenant while childTenantId was an arbitrary string — a tenant admin could attach ANY victim tenant as a child and then read its members (getAccessMatrix) or inject a privileged invite (sendHierarchyInvite); now requires members.manage on BOTH ends. payment recordDokuPending patched an existing order's amount + let the caller hijack its checkoutUrl with no owner check; now blocks cross-owner patch (guest + owner re-creates still pass). activity get was a public query returning private rows by id, bypassing the slice's own public-only contract (listAll is internal, listPublic filters) — now returns null for non-public rows. Deliberately SKIPPED: pages-cms listAll draft-visibility (non-tenant/actor data on a documented copy-source public read where the consumer owns the auth model — same call left as-is for the library twin).

NewerPage 2 / 17Older

Security

  • mcp/oauth-routes — `null` JSON token body no longer crashes the server (unauth DoS closed)
  • mcp/oauth-routes — CR/LF in redirect_uri rejected before new URL(), closing a setHeader ERR_INVALID_CHAR DoS
  • mcp/response-builders — composeInit + composeAdd now sanitize template/features/skills (no shell injection into copy-paste commands)

Fixes

  • cli/compose-state — malformed rr.json (non-array slices/features) no longer crashes `rr add` preflight
  • cli/dna — one corrupt/oddly-named .dna.json no longer crashes `rr graph` + lineage
  • app/agent-stream — malformed tool message returns cleanly instead of a raw 500
  • convex copy-source — all 21 feature dirs audited clean (no new fixes needed)

Cleanup

  • appshell — removed dead chromeInsets() getter
  • reel-editor — removed unused isXfadeSource() + tracksByKind() composition helpers
  • file-explorer — removed speculative mediaKind()/MediaKind (no callers)
  • admin — deleted 4 dead barrel files (redundant types re-export + 3 empty export{} scaffolds)
  • 32 of 37 audited slices were already minimal — no change; browser flagged-then-cleared on verification
  • audit-convex-api-paths — fails CI on any convex internal./api.features.* ref that doesn't resolve to a real export (the plural-path runtime null-throw class)
  • Fixes

    • proxy.ts — open redirect closed: external-demo dest built from the trusted base, so a crafted //host path can't escape the allowlisted Vercel origin
    • build-chat — malformed messages (non-array) returns a clean 400 instead of an unhandled 500
    • image-editor — open-image (loadImage) and remove-background now catch rejections + revoke leaked blob URLs
    • notion-shell — code-block copy + block copy-link clipboard writes guarded against unhandled rejection

    Fixes

    • MCP — reflected XSS in the OAuth consent page Deny control closed (onclick JS-string sink → <a href>)
    • CLI — boolean flag before a positional no longer swallows it (value-flag allowlist in both parseFlags copies)
    • code-editor + browser — file/browser tab switching now keyboard-operable (role=tab, Enter/Space)
    • file-explorer — list-view row converted to a Button (keyboard select/open) + rename field labelled
    • media-studio + image-editor — layer rows selectable via keyboard (guarded so nested inputs still work)

    Form labels (placeholder-only → aria-label)

    • landing-sections, vector-search, data-table, comments, browser, ai-chat, ai-router, user-management, reel-editor, app-store — input/textarea accessible names added
    • assistant — agent/skill/automation Field forms + image-editor properties panel (11 fields) labelled
    • ai-chat — chat action no longer calls a nonexistent api.aiConfig.get module (threw on every keyed request); default prompt inlined

    Accessibility depth

    • code-editor — tab close button labelled (with unsaved-changes state)
    • assistant — glyph picker buttons labelled
    • appshell — window-overview reveal + dock app links labelled
    • notion — editor toggle-block collapse/expand labelled
    • icon-picker — dialog-fallback gets a required (sr-only) DialogTitle

    Runtime null-throws (shipped broken)

    • ai-router — callModel logged usage via a plural internal API path → threw after paid spend; fixed to singular
    • cal-com-booking — calWebhook crashed on every delivery (plural mutations path); fixed to singular
    • resend-newsletter — broadcast fanout threw before sending (4 plural query/mutation paths); fixed to singular

    Security (copy-source)

    • user-management — linkTenant now requires members.manage on the child tenant too (closes cross-tenant disclosure + invite priv-esc)
    • doku-payment — recordDokuPending blocks cross-owner amount patch + checkout-URL hijack
    • activity — public get no longer returns private rows by id