Repository navigation
Conversation
…er undo (#145) * feat(recording): write initial metadata.json at session start Allows live viewers to fetch sessionStartTime before the session ends. The initial write happens after the sessionStart event is emitted (outside the synchronous commit block) and is best-effort (not awaited). The end-of-session writeMetadata then overwrites with final endTime + eventCount. - SessionMetadata.endTime now permits null (for in-progress sessions) - lifecycleController writes initialMetadata with endTime: null after sessionStart * feat(recording): tighten flush cadence and fix flush() promise semantics Reduces live-recording latency floor from ~5s to ~1s and ensures `await writer.flush()` actually means buffered events are on disk. - BUFFER_THRESHOLD: 20 -> 10 - FLUSH_INTERVAL_MS: 5000 -> 1000 - Public flush() always enqueues a real flush, no longer returns early from the debounce shortcut. Multiple concurrent callers each get their own flush; the first drains the buffer, subsequent ones find it empty and return. - _enqueueThresholdFlush() retains the original debounce for appendEvent-triggered and timer-triggered flushes. - _flushRequested is now honored after any lane work completes (not just flushes), so a threshold trigger during writeSnapshot or writeMetadata no longer waits for the next unrelated trigger. * feat(recording-viewer): faster tailer poll and add currentLineNo getter - Default pollIntervalMs reduced from 1000 to 250 ms; cuts the tailer's contribution to live latency by 4x. - New currentLineNo() getter lets SSE handlers coordinate per- connection disk catch-up with the shared tailer's broadcast position. Read-only; no mutable per-subscriber seek is exposed. - New startAtEnd option opts a tailer into starting at EOF with _lineNo derived from existing newlines, so SSE id: values stay aligned with file line numbers (needed for Last-Event-ID). * feat(recording-viewer): tail-limit query param on /events Adds opt-in ?tail=N parameter to GET /api/recordings/:id/events. Without the param the endpoint still returns the full archive (contract unchanged). Internally the new server/eventsReader.ts streams the JSONL file and keeps only the last N lines via a ringbuffer, so memory is O(N) regardless of file size. App.tsx's post-sessionEnd reload now uses ?tail=5000 to avoid re-crashing the tab when a long live session ends and the archive view attempts to load tens of thousands of events. * feat(recording-viewer): SSE catch-up with Last-Event-ID handoff Stops the initial-connect burst where the shared LiveTailer would re-broadcast every line in events.jsonl to every new subscriber. - LiveTailer now supports an opt-in startAtEnd mode that seeks to EOF and counts existing newlines to set _lineNo, then broadcasts only newly-appended content. - LiveTailerRegistry enables startAtEnd when events.jsonl is non-empty at construction time (the per-connection catch-up handles history), and leaves it off for sessions whose file doesn't yet exist (so the first appended content still gets emitted normally). - /events/stream now reads Last-Event-ID and ?tail=N before any I/O, subscribes to the shared tailer in buffer-only mode, reads catch-up from disk (Last-Event-ID resume, ?tail override, or default 5000), reads the gap up to the tailer's currentLineNo() to cover lines that landed during catch-up, drains the live buffer with dedupe via lineNo, then switches to direct-write mode. - No mutable per-subscriber seek is exposed; the shared tailer is strictly append-only-broadcast. * feat(recording-viewer): ringbuffer cap and RAF batching in useLiveSession Eliminates the O(n^2) render storm and unbounded memory growth that crashed the browser tab on long live sessions. Behavior changes: - Events are accumulated in a ref and flushed via requestAnimationFrame, so React renders at most ~60Hz regardless of SSE frame rate. jsdom fallback uses setTimeout(0) for tests. - A ringbuffer cap of MAX_LIVE_EVENTS (5000) drops the oldest events when exceeded, holding memory at a bounded ceiling. - sessionEnd flushes synchronously so the terminal state lands before EventSource closes; a pre-cap on incoming pending events protects against pathological catch-up bursts. - A closed flag short-circuits all callbacks after cleanup, preventing late-arriving SSE frames from setting state on an unmounted hook. - All refs reset on every setup AND cleanup, so a pending RAF can't leak events into a freshly-mounted session. - error is cleared on any successful flush, replacing the previous behavior where a stale 'Disconnected, retrying...' message could persist after reconnect. * feat(recording-viewer): useDeferredValue on live events for chart renders Wraps live.events in useDeferredValue before threading it through displayedEvents into the chart, timeline, and event-stream children. React can now interrupt expensive renders of stale snapshots when a newer SSE batch arrives, smoothing out sustained-load behavior. live.latestEventTimestamp stays non-deferred so the annotation hotkeys anchor at the correct wall-clock position. * feat(recording-viewer): virtualize EventStream with react-virtuoso Replaces the non-virtualized DOM list (one node per event) with Virtuoso so the live event list only renders the visible window plus a small overscan buffer. Memory and reconcile cost are bounded regardless of stream length. - followOutput='smooth' auto-scrolls to bottom on new items, gated by atBottomStateChange so the user can read older events without being yanked back. - isScrolling drops the follow-playback toggle when the user actively scrolls away, replacing the old listRef scroll listener. - scrollToTimestamp and follow-playback now resolve to a stream index via in-memory binary-ish search and call Virtuoso's scrollToIndex, replacing the previous querySelectorAll('[data-timestamp]') traversal. - computeItemKey preserves the existing per-row key shape. * test: add coverage for the new live-viewer behaviors - recordingsApi.tail: ?tail=N param boundaries (full, N, 0, oversize, invalid, negative). - recordingsApi.sse.catchup: default tail on connect, ?tail override, Last-Event-ID resume, empty catch-up when ID beyond file, 400 on bad tail, no-duplicate guarantee on overlap. - liveTailer: currentLineNo getter, startAtEnd skip-historical-only, startAtEnd doesn't drop content written after construction. - eventsReader: readLastNLines and readLinesAfter behavior including empty-line handling and limit boundaries. - sessionRecorder: initial metadata.json written at session start with endTime: null; final write overwrites with concrete endTime + count. * fix(recording-viewer): address codex final-review findings 1. useLiveSession: per-effect closures replace shared refs. The previous closedRef was reset to false on every effect run, allowing a queued onmessage from the previous session-id to write into the new session's buffers after cleanup ran. 2. EventStream: stable content-derived row keys. expandedTerminals and computeItemKey now use `${timestamp}-${type}` instead of array index, so terminal expansion state stays attached to the right row when the live ringbuffer trims off old events and remaining indexes shift. 3. EventStream: Virtuoso now has a determinate viewport. .event-list uses height (not max-height) so the virtualizer measures correctly; the Virtuoso element itself is given style={{ height: '100%' }}. 4. readLastNLines(0): no longer short-circuits with endLineNo=0. Streams the file to compute the real cursor so the SSE gap-read doesn't treat tail=0 as 'no lines exist yet' and replay the full file as a gap. 5. LiveTailer: replaced the first-poll startAtEnd heuristic with explicit initialOffset / initialLineNo / initialMtimeNs options. The registry now computes these synchronously at acquire time via statSync + sync newline-count, so lines appended between construction and the first poll are still emitted instead of being silently skipped as 'historical content'. Tests added: SSE tail=0 produces no historical flood; registry seeds cursor at acquire time so lines appended before first poll still emit; LiveTailer initial cursor + race coverage; readLastNLines(0) endLineNo contract documented. * fix(recording-viewer): close two follow-up codex findings 1. Direct-mode SSE writes now dedupe via sentMaxLineNo. The variable was hoisted out of the catch-up async block into the outer SSE handler scope and the tailer subscribe callback checks it before emitting in direct mode. This closes the race where a tailer poll running after directMode flips could re-emit a line that catch-up already sent (when the seeded cursor was behind that line at gap-read time). 2. EventStream now derives each event's row key from a per-instance WeakMap-backed keyer instead of timestamp+type. Same-millisecond same-type events (textChange / diagnostics during bursts) no longer collide; Virtuoso's computeItemKey can safely reuse rows only when the underlying event object is the same. WeakMap lets dropped ringbuffer events GC naturally. * feat(recording-viewer): undo/redo for live markers via Cmd/Ctrl+Z Adds a semantic-queue mutation controller that lets the user undo and redo struggle/context markers while a live session is recording. Cmd/Ctrl+Z walks the history back, Cmd/Ctrl+Shift+Z (and Ctrl+Y on Windows/Linux) re-applies the most recently undone marker. Any new marker invalidates the redo stack. Server: new DELETE /api/recordings/:sessionId/annotations/:id endpoint (synchronous read-modify-write, live-mode carve-out alongside POST). Auth gate updated to allow both shapes without RECORDING_VIEWER_ALLOW_WRITE=1. Client: createAnnotationController owns annotation state via a single Promise-chained queue of semantic operations. addLabel / undoLast / redoLast / reset all enqueue and capture both a generation counter and the sessionId so cross-session writes can never leak after a navigation or session-end reload. useAnnotationMutations is a thin React adapter. useLiveHotkeys extended with onUndo / onRedo callbacks; handler logic extracted to a pure function for unit testing. Also: fix latent bug where saveAnnotations dropped failed-PUT responses without rollback, now rolls back guarded by sessionId and reference-equal state check. Tests: 58 new cases (annotation controller, hotkey handler matrix, DELETE endpoint + live-mode carve-out, codex-flagged races: temp-id-undo, failed-undo-then-redo, reset mid-flight, real-load ordering). * docs(changelog): add unreleased entry for live viewer improvements
…ut (#148) * feat(ws-statusbar): gate visibility on authentication state Default behavior previously surfaced a red WS Disconnected indicator when the user was logged out, suggesting a connection failure even though no WebSocket existed. WebSocketStatusBarService now accepts an external authentication signal and hides itself when logged out, unless the artemis.showWebSocketStatusBar setting is explicitly enabled for diagnostics. * feat(extension): sync ws statusbar auth state on login/logout/startup Calls websocketStatusBarService.setAuthenticated(...) from the three auth entry points: updateAuthContext (login/logout), the initial-auth try block, and the catch fallback. Without this hook the status bar would remain in its default unauthenticated state across login transitions. * docs(changelog): note ws statusbar hide-on-logout fix
…pdown to input, scroll shadows, consistent tag column (#149) - Conversations now sorted by lastActivity desc at snapshot time - Dropdown height tracks actual input section via ResizeObserver + CSS var - Scroll shadows (top + bottom) on each list, pure CSS, no JS - Course tag column reserves min-width 4em so untagged rows align consistently - Diagnostics report includes courseId per exercise for orphan-course debugging
…504) (#150) * docs(spec): Iris API migration to unified sessions (PR #12504) Design for adapting the extension's Iris API client after Artemis develop merged the per-context session endpoints into one unified surface. Two codex review rounds; explicitly approved after the resolver-extraction fix and test-file scope expansion. * docs(plan): Iris API migration to unified sessions 9-task TDD plan covering types, helpers, primitives, composition layer, caller migration, test stub migration (with onFirstCall/onSecondCall discipline for the dual getChatMessages calls), legacy method deletion, and acceptance. Codex-approved after 2 rounds. * feat(iris): add IrisChatMode and IrisChatSessionSummary types * feat(iris): add contextToIrisMode helper * refactor(iris): extract resolveCourseIdFromContext into shared module * feat(iris-api): add unified chat session primitives * refactor(iris): orchestrate sessions via unified overview + per-session messages * refactor(iris): dispatch WebSocket session client via contextToIrisMode * test(e2e): point uncommittedChanges helper at unified Iris API * refactor(iris-api): remove legacy per-context session methods * chore(changelog): note Iris API migration to unified sessions * chore(changelog): rephrase Iris API entry to user-facing wording
* feat: server-side problem statement rendering (v2, React stack) Integrate Artemis POST /api/exercise/problem-statement/render endpoint into the React-based extension with progressive enhancement: - ProblemStatementRenderService: LRU cache, feature detection, race guards - ProblemStatement.tsx: accepts serverRenderedHtml prop, skips client processing - CSS bridge maps VS Code theme variables to Artemis server CSS variables - Typed ProblemStatementRendered message contract - Background render triggered on exercise open, result sent via postMessage * debug: add SSR/Client indicator to Exercise Description header * debug: add console.log for SSR message reception in webview * fix: include SSR result in exerciseDetailInit message to avoid race condition * chore: remove debug indicators and console.log from SSR integration * fix: improve CSS bridge to match VS Code theme (fonts, headings, code, task colors) * feat: add dev-mode CSS toggle to switch between VS Code bridge and raw server CSS * refactor: move SSR CSS toggle button to Developer Tools panel * fix: keep server pink for inline code, use semi-transparent background for visibility * fix: ensure server pink inline code color wins over client CSS module styles * fix: constrain PlantUML SVG diagrams to fit within container width * fix: brighten PlantUML SVG diagrams for better visibility in dark themes * feat: add 'View SSR HTML' button in dev tools to inspect server-rendered HTML * fix: increase SVG brightness filter to 1.8 for visible diagram lines in dark mode * fix: target SVG link/entity elements specifically for dark mode visibility * debug: log darkMode value in SSR request * fix: scope PlantUML SVG overrides to dark mode, re-trigger SSR on reload Dark-mode CSS overrides for PlantUML diagrams (stroke: #ccc/#aaa) made lines invisible in light mode. Scoped to body.vscode-dark and body.vscode-high-contrast only. Reload button dropped back to client render because handleReloadExerciseDetail never re-triggered background SSR. Added backgroundRenderProblemStatement to WebViewActionHandler interface and call it after sendInitData in the reload path. * fix: re-render SSR on VS Code theme change When the user switches between light/dark themes, invalidate the SSR cache and re-request from the server with the correct darkMode value. Without this, switching to light mode kept showing the dark-themed SVG (light strokes on transparent background = invisible on white). * fix: darken PlantUML arrows in light mode for better visibility The Artemis light theme uses #808080 for extension/composition arrows, which is too faint on white. Darken to #444 in vscode-light and vscode-high-contrast-light, scoped via attribute selector to only target the gray arrows (not the already-black dependency arrows). * fix: match Artemis web PlantUML appearance in light mode Server SSR produces muted SVGs (#808080 arrows/text, 0.5px borders) while the Artemis web client renders green arrows, green attribute text, and visible box borders. CSS overrides now: - Bump entity border stroke-width from 0.5 to 1 - Recolor #808080 arrows to #28a745 (Artemis green) - Recolor #808080 attribute/method text to green - Increase arrow stroke-width to 1.5 * fix: increase PlantUML entity border stroke-width to 2px * feat: add SSR preview panel, remove all PlantUML SVG CSS overrides Add "Preview SSR" dev tools button that opens a webview panel showing the rendered server HTML (including inline SVG diagrams). Remove all PlantUML SVG CSS overrides (dark-mode stroke lightening, light-mode green recoloring, border-width bumps). The server delivers the correct colors via the Artemis PlantUML themes — the same code path as the web client. CSS overrides were fragile and unnecessary. * fix: remove width:auto and !important overrides from PlantUML SVGs Stop overriding SVG width and remove !important flags. Match the server's own CSS exactly (max-width:100%;height:auto). The extra width:auto !important was likely causing SVG stroke rendering differences between the sidebar and SSR preview panel. * fix: allow inline style attributes in CSP for server-rendered SVGs The CSP style-src directive with nonce blocked ALL inline style= attributes, including those on SVG elements from the server-rendered problem statement. This made SVG strokes/borders invisible because PlantUML uses style="stroke:#000;stroke-width:0.5" on rect/path elements while fill colors use SVG attributes (which aren't affected). Add style-src-attr 'unsafe-inline' to allow inline style attributes while keeping nonce protection for <style> elements. * fix: pass feedbacks from latest submission to SSR render Was accessing submissions[0] (oldest) instead of the latest submission sorted by ID. The feedbacks with testCase.id were on the latest result but we were reading from the first array element. Now matches the same sorting logic as exerciseDataLoader. * feat: bridge server task CSS for icons, stats, and status colors The server's embedded <style> block is blocked by CSP (no nonce). Replicate the task-related CSS rules in our CSS module so checkmarks, crosses, x/y stats, and status colors render correctly. Uses VS Code theme variables with server CSS variable fallbacks. * fix: allow server <style> through CSP, remove replicated CSS Replace nonce-based style-src with 'unsafe-inline' so the server's embedded <style> block is no longer blocked by CSP. This means the server's own CSS for tasks, icons, stats, diagrams, and typography is applied directly — no need to replicate it in our CSS module. Removed ~50 lines of bridged server CSS (task icons, stats colors, SVG layout). Only the CSS variable bridge remains (mapping VS Code theme vars to server CSS custom properties). * fix: allow data: URIs in img-src CSP for task status icons * refactor: remove CSS bridge, use pure server rendering for SSR Server-rendered content now uses only the server's own CSS — no VS Code theme variable bridge, no heading overrides, no inline code fixes. The server <style> comes through CSP directly. Removed: CSS variable bridge, heading/code overrides, useServerCssOnly toggle and prop. Client-rendered fallback still uses .problemStatement. Note: dark mode will need server-side CSS fixes (fallback colors are light-mode only). * feat: add Preview Light / Preview Dark buttons for SSR debugging Each button fetches fresh SSR from the server with explicit darkMode flag, bypassing the cache. Opens in a preview panel with matching background color. Useful for tuning the server's dark mode CSS. Also adds darkModeOverride parameter to render service for this. * fix: pass CSP nonce to interactive script for task feedback modal The dynamically created script element for the server's interactive JS was blocked by CSP (script-src requires nonce). Pass the nonce via data-csp-nonce on the root element and apply it to the script. * refactor: remove client-side problem statement rendering Server-side rendering is now the only rendering path. Removed: - problemStatementProcessor.ts (Markdown/KaTeX/PlantUML client pipeline) - plantUmlCommands.ts (inline PlantUML rendering via extension API) - PlantUmlRendered/PlantUmlError extension messages - RenderPlantUmlInline webview command - All .problemStatement CSS rules (200+ lines of client styling) - vscodeApi prop from ProblemStatementProps When SSR is unavailable, raw markdown is shown as plain text fallback. The ProblemStatement component is now ~60 lines (was ~160). * refactor: use VS Code language instead of Artemis user profile langKey Simpler and more consistent — the extension UI is already in the VS Code language, so the SSR content should match. Removed userLangKey parameter from render/debouncedRender, now always uses vscode.env.language. * refactor: hardcode locale to 'en' since extension is English-only * refactor: rename interactive to includeJs, add includeCss to request DTO Match server-side rename of interactive → includeJs for consistency with the new includeCss flag. Both default to true on the server. * feat: show skeleton loading state for problem statement instead of raw markdown * fix: remove dead code, consolidate CSS, fix ExamExerciseDetail SSR regression * feat(problem-statement): use iframe srcdoc for SSR rendering Replace dangerouslySetInnerHTML with iframe srcdoc to render the server-provided HTML document. This enables KaTeX and interactive scripts to execute properly, matching how iOS WKWebView handles SSR content. Add auto-resize and frame-src CSP directive. * feat(problem-statement): render KaTeX client-side via bundled npm package Replace the iframe approach with dangerouslySetInnerHTML + client-side KaTeX rendering. The server provides formula placeholders which are rendered after DOM injection using the bundled katex npm package. Copy KaTeX assets to dist/katex/ at build time. Strip server KaTeX script/link tags and extract body content from the full HTML document. * refactor(problem-statement): memoize body HTML and fix regex robustness Use useMemo for bodyHtml derivation to avoid regex recomputation on every render. Use [\s\S]*? instead of [^<]* in KaTeX script stripping for multiline content. Remove duplicate katex.min.css import (already in index.tsx). * fix(problem-statement): use forEach over NodeList to avoid iterator requirement * test(exercise-detail): update problem-statement test to expect SSR-rendered HTML The ProblemStatement component no longer renders raw markdown; it waits for server-rendered HTML via problemStatementRendered message and shows a skeleton until then. Simulate that message in the test. * feat(problem-statement): request inlineImages from server for SSR VS Code webviews have no JWT cookie, so absolute image URLs returned by the SSR endpoint cannot be loaded. Hardcode inlineImages=true so the server embeds images as base64 data URIs. Include the flag in the cache input hash to invalidate stale entries if the value ever changes. * chore(api): drop unused submitStudentExam (resurrected by dev merge) The function was removed in dev cleanup f2922cd but came back via the old iris-thaumantias path during the dev merge into this branch. No callers exist. * chore(esbuild): drop dead copyKatexAssets KaTeX is consumed via npm import in webview/index.tsx (CSS) and ProblemStatement.tsx (formula rendering). Nothing references dist/katex, so the build-time copy step is dead. * chore(plantuml): drop dead extension-side PlantUML stack Webview no longer renders PlantUML client-side (server-side rendering handles it now), so the extension-side counterparts are dead: - ArtemisApiService.renderPlantUmlToSvg - artemis.renderPlantUmlFromWebview command + registration - processPlantUml util + re-export - plantUmlProcessor + artemisApi PlantUML unit tests - LogCategory.PLANTUML (no remaining users) - baseWebviewProvider EVENT_TYPES entries for plantUmlRendered / plantUmlError * chore(deps): drop unused marked package Top-level marked was only consumed by problemStatementProcessor, which this PR removed. Streamdown / mermaid still pull their own pinned versions transitively, which is fine. - package.json: drop marked + @types/marked - knip.json: drop @types/marked ignore - src/extension/types/ambient/marked.d.ts: deleted - package-lock.json: regenerated * refactor(ssr): drop interactiveScript plumbing and unused render helpers The iframe srcdoc design that needed interactiveScript was replaced by dangerouslySetInnerHTML + bundled-KaTeX rendering. The script field was never executed in the new path, so all of its plumbing was dead. - Drop interactiveScript through DTO, ServerRenderResult, app state, webview provider, message contracts, and ExerciseDetailView state. - Drop the data-csp-nonce attribute on the root element (was only for the dropped script). - Drop the orphan source: 'server' field on ServerRenderResult. - Hardcode includeJs: false in the request builder (server no longer needs to emit the script). - Drop unused debouncedRender (and debounceTimers/pendingResolvers fields), invalidateExercise, and isServerAvailable from the service. - Drop the unused isExamExercise param from render() — exam view uses its own plaintext fallback. - Drop PreviewSsrHtml command + handler (no senders; Fresh SSR Preview is the only entry point). - Update stale comment in ProblemStatement.tsx; remove a CSP comment block whose wording was tripping the csp.test.ts security regex. * fix(exam): render problem statement as escaped plaintext Exam mode never goes through the SSR pipeline (it short-circuits in ProblemStatementRenderService), so passing raw markdown into the SSR-only ProblemStatement component injected unrendered markdown as HTML. Replace with an inline Container that shows a notice and the markdown inside a <pre> via React text (escaped), no dangerouslySetInnerHTML. This is a holding pattern until exam mode is removed entirely. * chore(ssr): drop unused exports (knip pass) - domain/index.ts: drop the four problem-statement-rendering re-exports (callers import directly from problemStatementRendering.ts). - problemStatementRenderService: unexport mapFeedbacksToTestInputs, buildResultSummary, ServerRenderResult — only consumed internally. * fix(ssr): guard against exam markdown reaching the render endpoint _backgroundRenderProblemStatement is called unconditionally on VS Code theme changes (registerThemeListener). When an exam exercise is the active view, currentExerciseData structurally satisfies the ExerciseDetailsResponse cast, so the active exam markdown was being POSTed to /api/exercise/problem-statement/render even though the exam view itself ignores the response. Bail out at the top of the renderer if the active state is not 'exercise-detail'. * fix(ssr): dispose render service config listener ProblemStatementRenderService registered an onDidChangeConfiguration listener in its constructor that was never disposed. The long-lived instance in artemisWebviewProvider leaked one listener for the lifetime of the host; the short-lived instances created per FreshSsrPreview leaked one listener per invocation. - Implement vscode.Disposable on the service; capture and dispose the config listener. - Push the long-lived instance onto provider._disposables. - Wrap FreshSsrPreview render in try/finally to dispose the short-lived instance. * refactor(problem-statement): drop dead downloadLinks plumbing, tighten tests The downloadLinks prop was permanently fed an empty array ('simplified - in real implementation would parse from markdown'). The Downloads section, its CSS, its tests, and the onDownload callback were all dead. - Drop downloadLinks + onDownload from ProblemStatementProps. - Drop the Downloads section and its 3 CSS classes. - Drop the 4 download-related tests. - Move vi.useRealTimers() into afterEach so a fake-timer test failure cannot leak into the next test. - Make the link-href test actually assert the href attribute. - Add a small test covering the KaTeX <script>/<link> stripping. * refactor(messages): name the rendered-problem-statement payload type once The { html: string } shape was duplicated in exerciseDetailInit and problemStatementRendered. Pull it into a named RenderedProblemStatementPayload so the shape is described in one place. * fix(api): validate problem-statement render DTO before returning The DTO's html field is injected via dangerouslySetInnerHTML. Add a small runtime type guard for html / contentHash / rendererVersion so a malformed server response surfaces as a clear error instead of silently turning into 'undefined' HTML in the DOM. * docs(ssr): correct misleading log wording The unsupported-server flag is reset on Artemis URL config change, so 'disabled permanently' was misleading. Reword to 'disabled until config change'. * test(exercise-detail): use ExtensionMsg constant instead of magic string Drop the raw 'problemStatementRendered' string in favour of the discriminated-union constant so a future rename of the message type fails at compile time, not at runtime. * test(ssr): add unit tests for ProblemStatementRenderService Cover behaviour that was previously untested: - short-circuit on missing markdown or exerciseId - request shape (includeJs:false, locale, darkMode, inlineImages) - relative URL rewriting against the configured server - cache hit + invalidation - 404 / 405 / 501 permanently disabling within a session - transient 500 retries - config-change resets the unsupported-server flag and clears cache - dispose() releases the config listener * refactor(ssr): drop dead resultSummary payload, consolidate feedbacks extraction Audit against Artemis backend (ProblemStatementRenderingService.java) revealed that resultSummary only emits a data-result DOM attribute, consumed exclusively by interactive.js. Since the extension hardcodes includeJs:false, every field of ResultSummaryInput was dead on arrival. testResults stays — load-bearing for task icons, X-of-N stats, and PlantUML coloring regardless of includeJs. Also collapses the latestSubmission/latestResult extraction logic that was duplicated across artemisWebviewProvider, utilityCommands, and exerciseDataLoader into a single pickHighestId helper. Service render() now takes an options object and walks participation internally, so call sites stop reaching into submissions and results. Codex flagged a related risk: the duplicated call sites sorted by id while buildResultSummary used completionDate, which would have selected a different result than the feedbacks payload. Dropping resultSummary eliminates that inconsistency. * fix(ssr): enforce Artemis validation constraints on testResults payload Backend rejects requests that violate ProblemStatementRenderRequestDTO / TestFeedbackInputDTO validation (testResults @SiZe(max=100), testName @SiZe(max=500), message @SiZe(max=5000), duplicate testIds → 422). The mapper now: - clamps testName to 500 chars and message to 5000 chars - dedupes by testId (first occurrence wins) - caps testResults at 100 entries Long compile/test output in feedback.detailText was the realistic trigger. It easily exceeds 5000 chars and would have caused the entire render request to fail server-side validation, leaving the user with no rendered problem statement. Adds unit coverage for the new constraints and a dedicated test file for participationHelpers (pickHighestId + extractLatestFeedbacks). * fix(ssr): short-circuit render when markdown violates DTO validation Backend ProblemStatementRenderRequestDTO also constrains markdown: @SiZe(max=100_000) and a null-byte regex reject. A failed request would not be classified as 'server unsupported' (non-{404,405,501}), so it would retry on the next render attempt and keep failing. Skipping client-side avoids the wasted round-trip and the noisy log loop. Adds two unit tests covering oversize and null-byte markdown.
* feat: server-side problem statement rendering (v2, React stack) Integrate Artemis POST /api/exercise/problem-statement/render endpoint into the React-based extension with progressive enhancement: - ProblemStatementRenderService: LRU cache, feature detection, race guards - ProblemStatement.tsx: accepts serverRenderedHtml prop, skips client processing - CSS bridge maps VS Code theme variables to Artemis server CSS variables - Typed ProblemStatementRendered message contract - Background render triggered on exercise open, result sent via postMessage * debug: add SSR/Client indicator to Exercise Description header * debug: add console.log for SSR message reception in webview * fix: include SSR result in exerciseDetailInit message to avoid race condition * chore: remove debug indicators and console.log from SSR integration * fix: improve CSS bridge to match VS Code theme (fonts, headings, code, task colors) * feat: add dev-mode CSS toggle to switch between VS Code bridge and raw server CSS * refactor: move SSR CSS toggle button to Developer Tools panel * fix: keep server pink for inline code, use semi-transparent background for visibility * fix: ensure server pink inline code color wins over client CSS module styles * fix: constrain PlantUML SVG diagrams to fit within container width * fix: brighten PlantUML SVG diagrams for better visibility in dark themes * feat: add 'View SSR HTML' button in dev tools to inspect server-rendered HTML * fix: increase SVG brightness filter to 1.8 for visible diagram lines in dark mode * fix: target SVG link/entity elements specifically for dark mode visibility * debug: log darkMode value in SSR request * fix: scope PlantUML SVG overrides to dark mode, re-trigger SSR on reload Dark-mode CSS overrides for PlantUML diagrams (stroke: #ccc/#aaa) made lines invisible in light mode. Scoped to body.vscode-dark and body.vscode-high-contrast only. Reload button dropped back to client render because handleReloadExerciseDetail never re-triggered background SSR. Added backgroundRenderProblemStatement to WebViewActionHandler interface and call it after sendInitData in the reload path. * fix: re-render SSR on VS Code theme change When the user switches between light/dark themes, invalidate the SSR cache and re-request from the server with the correct darkMode value. Without this, switching to light mode kept showing the dark-themed SVG (light strokes on transparent background = invisible on white). * fix: darken PlantUML arrows in light mode for better visibility The Artemis light theme uses #808080 for extension/composition arrows, which is too faint on white. Darken to #444 in vscode-light and vscode-high-contrast-light, scoped via attribute selector to only target the gray arrows (not the already-black dependency arrows). * fix: match Artemis web PlantUML appearance in light mode Server SSR produces muted SVGs (#808080 arrows/text, 0.5px borders) while the Artemis web client renders green arrows, green attribute text, and visible box borders. CSS overrides now: - Bump entity border stroke-width from 0.5 to 1 - Recolor #808080 arrows to #28a745 (Artemis green) - Recolor #808080 attribute/method text to green - Increase arrow stroke-width to 1.5 * fix: increase PlantUML entity border stroke-width to 2px * feat: add SSR preview panel, remove all PlantUML SVG CSS overrides Add "Preview SSR" dev tools button that opens a webview panel showing the rendered server HTML (including inline SVG diagrams). Remove all PlantUML SVG CSS overrides (dark-mode stroke lightening, light-mode green recoloring, border-width bumps). The server delivers the correct colors via the Artemis PlantUML themes — the same code path as the web client. CSS overrides were fragile and unnecessary. * fix: remove width:auto and !important overrides from PlantUML SVGs Stop overriding SVG width and remove !important flags. Match the server's own CSS exactly (max-width:100%;height:auto). The extra width:auto !important was likely causing SVG stroke rendering differences between the sidebar and SSR preview panel. * fix: allow inline style attributes in CSP for server-rendered SVGs The CSP style-src directive with nonce blocked ALL inline style= attributes, including those on SVG elements from the server-rendered problem statement. This made SVG strokes/borders invisible because PlantUML uses style="stroke:#000;stroke-width:0.5" on rect/path elements while fill colors use SVG attributes (which aren't affected). Add style-src-attr 'unsafe-inline' to allow inline style attributes while keeping nonce protection for <style> elements. * fix: pass feedbacks from latest submission to SSR render Was accessing submissions[0] (oldest) instead of the latest submission sorted by ID. The feedbacks with testCase.id were on the latest result but we were reading from the first array element. Now matches the same sorting logic as exerciseDataLoader. * feat: bridge server task CSS for icons, stats, and status colors The server's embedded <style> block is blocked by CSP (no nonce). Replicate the task-related CSS rules in our CSS module so checkmarks, crosses, x/y stats, and status colors render correctly. Uses VS Code theme variables with server CSS variable fallbacks. * fix: allow server <style> through CSP, remove replicated CSS Replace nonce-based style-src with 'unsafe-inline' so the server's embedded <style> block is no longer blocked by CSP. This means the server's own CSS for tasks, icons, stats, diagrams, and typography is applied directly — no need to replicate it in our CSS module. Removed ~50 lines of bridged server CSS (task icons, stats colors, SVG layout). Only the CSS variable bridge remains (mapping VS Code theme vars to server CSS custom properties). * fix: allow data: URIs in img-src CSP for task status icons * refactor: remove CSS bridge, use pure server rendering for SSR Server-rendered content now uses only the server's own CSS — no VS Code theme variable bridge, no heading overrides, no inline code fixes. The server <style> comes through CSP directly. Removed: CSS variable bridge, heading/code overrides, useServerCssOnly toggle and prop. Client-rendered fallback still uses .problemStatement. Note: dark mode will need server-side CSS fixes (fallback colors are light-mode only). * feat: add Preview Light / Preview Dark buttons for SSR debugging Each button fetches fresh SSR from the server with explicit darkMode flag, bypassing the cache. Opens in a preview panel with matching background color. Useful for tuning the server's dark mode CSS. Also adds darkModeOverride parameter to render service for this. * fix: pass CSP nonce to interactive script for task feedback modal The dynamically created script element for the server's interactive JS was blocked by CSP (script-src requires nonce). Pass the nonce via data-csp-nonce on the root element and apply it to the script. * refactor: remove client-side problem statement rendering Server-side rendering is now the only rendering path. Removed: - problemStatementProcessor.ts (Markdown/KaTeX/PlantUML client pipeline) - plantUmlCommands.ts (inline PlantUML rendering via extension API) - PlantUmlRendered/PlantUmlError extension messages - RenderPlantUmlInline webview command - All .problemStatement CSS rules (200+ lines of client styling) - vscodeApi prop from ProblemStatementProps When SSR is unavailable, raw markdown is shown as plain text fallback. The ProblemStatement component is now ~60 lines (was ~160). * refactor: use VS Code language instead of Artemis user profile langKey Simpler and more consistent — the extension UI is already in the VS Code language, so the SSR content should match. Removed userLangKey parameter from render/debouncedRender, now always uses vscode.env.language. * refactor: hardcode locale to 'en' since extension is English-only * refactor: rename interactive to includeJs, add includeCss to request DTO Match server-side rename of interactive → includeJs for consistency with the new includeCss flag. Both default to true on the server. * feat: show skeleton loading state for problem statement instead of raw markdown * fix: remove dead code, consolidate CSS, fix ExamExerciseDetail SSR regression * feat(problem-statement): use iframe srcdoc for SSR rendering Replace dangerouslySetInnerHTML with iframe srcdoc to render the server-provided HTML document. This enables KaTeX and interactive scripts to execute properly, matching how iOS WKWebView handles SSR content. Add auto-resize and frame-src CSP directive. * feat(problem-statement): render KaTeX client-side via bundled npm package Replace the iframe approach with dangerouslySetInnerHTML + client-side KaTeX rendering. The server provides formula placeholders which are rendered after DOM injection using the bundled katex npm package. Copy KaTeX assets to dist/katex/ at build time. Strip server KaTeX script/link tags and extract body content from the full HTML document. * refactor(problem-statement): memoize body HTML and fix regex robustness Use useMemo for bodyHtml derivation to avoid regex recomputation on every render. Use [\s\S]*? instead of [^<]* in KaTeX script stripping for multiline content. Remove duplicate katex.min.css import (already in index.tsx). * fix(problem-statement): use forEach over NodeList to avoid iterator requirement * test(exercise-detail): update problem-statement test to expect SSR-rendered HTML The ProblemStatement component no longer renders raw markdown; it waits for server-rendered HTML via problemStatementRendered message and shows a skeleton until then. Simulate that message in the test. * feat(problem-statement): request inlineImages from server for SSR VS Code webviews have no JWT cookie, so absolute image URLs returned by the SSR endpoint cannot be loaded. Hardcode inlineImages=true so the server embeds images as base64 data URIs. Include the flag in the cache input hash to invalidate stale entries if the value ever changes. * chore(api): drop unused submitStudentExam (resurrected by dev merge) The function was removed in dev cleanup f2922cd but came back via the old iris-thaumantias path during the dev merge into this branch. No callers exist. * chore(esbuild): drop dead copyKatexAssets KaTeX is consumed via npm import in webview/index.tsx (CSS) and ProblemStatement.tsx (formula rendering). Nothing references dist/katex, so the build-time copy step is dead. * chore(plantuml): drop dead extension-side PlantUML stack Webview no longer renders PlantUML client-side (server-side rendering handles it now), so the extension-side counterparts are dead: - ArtemisApiService.renderPlantUmlToSvg - artemis.renderPlantUmlFromWebview command + registration - processPlantUml util + re-export - plantUmlProcessor + artemisApi PlantUML unit tests - LogCategory.PLANTUML (no remaining users) - baseWebviewProvider EVENT_TYPES entries for plantUmlRendered / plantUmlError * chore(deps): drop unused marked package Top-level marked was only consumed by problemStatementProcessor, which this PR removed. Streamdown / mermaid still pull their own pinned versions transitively, which is fine. - package.json: drop marked + @types/marked - knip.json: drop @types/marked ignore - src/extension/types/ambient/marked.d.ts: deleted - package-lock.json: regenerated * refactor(ssr): drop interactiveScript plumbing and unused render helpers The iframe srcdoc design that needed interactiveScript was replaced by dangerouslySetInnerHTML + bundled-KaTeX rendering. The script field was never executed in the new path, so all of its plumbing was dead. - Drop interactiveScript through DTO, ServerRenderResult, app state, webview provider, message contracts, and ExerciseDetailView state. - Drop the data-csp-nonce attribute on the root element (was only for the dropped script). - Drop the orphan source: 'server' field on ServerRenderResult. - Hardcode includeJs: false in the request builder (server no longer needs to emit the script). - Drop unused debouncedRender (and debounceTimers/pendingResolvers fields), invalidateExercise, and isServerAvailable from the service. - Drop the unused isExamExercise param from render() — exam view uses its own plaintext fallback. - Drop PreviewSsrHtml command + handler (no senders; Fresh SSR Preview is the only entry point). - Update stale comment in ProblemStatement.tsx; remove a CSP comment block whose wording was tripping the csp.test.ts security regex. * fix(exam): render problem statement as escaped plaintext Exam mode never goes through the SSR pipeline (it short-circuits in ProblemStatementRenderService), so passing raw markdown into the SSR-only ProblemStatement component injected unrendered markdown as HTML. Replace with an inline Container that shows a notice and the markdown inside a <pre> via React text (escaped), no dangerouslySetInnerHTML. This is a holding pattern until exam mode is removed entirely. * chore(ssr): drop unused exports (knip pass) - domain/index.ts: drop the four problem-statement-rendering re-exports (callers import directly from problemStatementRendering.ts). - problemStatementRenderService: unexport mapFeedbacksToTestInputs, buildResultSummary, ServerRenderResult — only consumed internally. * fix(ssr): guard against exam markdown reaching the render endpoint _backgroundRenderProblemStatement is called unconditionally on VS Code theme changes (registerThemeListener). When an exam exercise is the active view, currentExerciseData structurally satisfies the ExerciseDetailsResponse cast, so the active exam markdown was being POSTed to /api/exercise/problem-statement/render even though the exam view itself ignores the response. Bail out at the top of the renderer if the active state is not 'exercise-detail'. * fix(ssr): dispose render service config listener ProblemStatementRenderService registered an onDidChangeConfiguration listener in its constructor that was never disposed. The long-lived instance in artemisWebviewProvider leaked one listener for the lifetime of the host; the short-lived instances created per FreshSsrPreview leaked one listener per invocation. - Implement vscode.Disposable on the service; capture and dispose the config listener. - Push the long-lived instance onto provider._disposables. - Wrap FreshSsrPreview render in try/finally to dispose the short-lived instance. * refactor(problem-statement): drop dead downloadLinks plumbing, tighten tests The downloadLinks prop was permanently fed an empty array ('simplified - in real implementation would parse from markdown'). The Downloads section, its CSS, its tests, and the onDownload callback were all dead. - Drop downloadLinks + onDownload from ProblemStatementProps. - Drop the Downloads section and its 3 CSS classes. - Drop the 4 download-related tests. - Move vi.useRealTimers() into afterEach so a fake-timer test failure cannot leak into the next test. - Make the link-href test actually assert the href attribute. - Add a small test covering the KaTeX <script>/<link> stripping. * refactor(messages): name the rendered-problem-statement payload type once The { html: string } shape was duplicated in exerciseDetailInit and problemStatementRendered. Pull it into a named RenderedProblemStatementPayload so the shape is described in one place. * fix(api): validate problem-statement render DTO before returning The DTO's html field is injected via dangerouslySetInnerHTML. Add a small runtime type guard for html / contentHash / rendererVersion so a malformed server response surfaces as a clear error instead of silently turning into 'undefined' HTML in the DOM. * docs(ssr): correct misleading log wording The unsupported-server flag is reset on Artemis URL config change, so 'disabled permanently' was misleading. Reword to 'disabled until config change'. * test(exercise-detail): use ExtensionMsg constant instead of magic string Drop the raw 'problemStatementRendered' string in favour of the discriminated-union constant so a future rename of the message type fails at compile time, not at runtime. * test(ssr): add unit tests for ProblemStatementRenderService Cover behaviour that was previously untested: - short-circuit on missing markdown or exerciseId - request shape (includeJs:false, locale, darkMode, inlineImages) - relative URL rewriting against the configured server - cache hit + invalidation - 404 / 405 / 501 permanently disabling within a session - transient 500 retries - config-change resets the unsupported-server flag and clears cache - dispose() releases the config listener * refactor(ssr): drop dead resultSummary payload, consolidate feedbacks extraction Audit against Artemis backend (ProblemStatementRenderingService.java) revealed that resultSummary only emits a data-result DOM attribute, consumed exclusively by interactive.js. Since the extension hardcodes includeJs:false, every field of ResultSummaryInput was dead on arrival. testResults stays — load-bearing for task icons, X-of-N stats, and PlantUML coloring regardless of includeJs. Also collapses the latestSubmission/latestResult extraction logic that was duplicated across artemisWebviewProvider, utilityCommands, and exerciseDataLoader into a single pickHighestId helper. Service render() now takes an options object and walks participation internally, so call sites stop reaching into submissions and results. Codex flagged a related risk: the duplicated call sites sorted by id while buildResultSummary used completionDate, which would have selected a different result than the feedbacks payload. Dropping resultSummary eliminates that inconsistency. * fix(ssr): enforce Artemis validation constraints on testResults payload Backend rejects requests that violate ProblemStatementRenderRequestDTO / TestFeedbackInputDTO validation (testResults @SiZe(max=100), testName @SiZe(max=500), message @SiZe(max=5000), duplicate testIds → 422). The mapper now: - clamps testName to 500 chars and message to 5000 chars - dedupes by testId (first occurrence wins) - caps testResults at 100 entries Long compile/test output in feedback.detailText was the realistic trigger. It easily exceeds 5000 chars and would have caused the entire render request to fail server-side validation, leaving the user with no rendered problem statement. Adds unit coverage for the new constraints and a dedicated test file for participationHelpers (pickHighestId + extractLatestFeedbacks). * fix(ssr): short-circuit render when markdown violates DTO validation Backend ProblemStatementRenderRequestDTO also constrains markdown: @SiZe(max=100_000) and a null-byte regex reject. A failed request would not be classified as 'server unsupported' (non-{404,405,501}), so it would retry on the next render attempt and keep failing. Skipping client-side avoids the wasted round-trip and the noisy log loop. Adds two unit tests covering oversize and null-byte markdown. * docs(spec): test-results view tracking and per-task feedback modal Design doc for tracking when users view test results (overall and per-task) plus adding click-handling on [task] entries in the SSR'd problem statement to open a per-task feedback modal. * docs(spec): revise test-results tracking spec per codex review Address 6 findings from codex review of v1: - Switch wiring from CommandContext injection to provider-events + sessionRecorderWiring subscription (matches existing chat/intervention pattern, no CommandContext changes needed). - Move TestResultsOverlay rendering out of SubmissionStatus into ExerciseDetailView so the parent owns viewId/duration lifecycle and can enforce the single-modal invariant. - Use real ProblemStatement prop name (serverRenderedHtml, not html). - Reconcile close-event schema with lifecycle text: add participationId and resultId to close events; add 'replaced' to closeReason union; remove ambiguous 'synthetic close' wording. - Add explicit failure semantics for filterTestCasesByIds when testCase.id is missing, stale, or from a different participation. - Add command-contract tests covering WebCmd registration, COMMANDS_REQUIRING_PAYLOAD, and the provider-event wiring. * docs(spec): fix codex round-2 findings on test-results tracking spec - WebCmd payload schema now mirrors recorder event schema (participationId, resultId, required 4-variant closeReason). - Provider-event wiring is implementable via IProviderRegistry extension: add getArtemisWebviewProvider/setArtemisWebviewProvider with new IArtemisWebviewProvider interface. CommandContext stays unchanged. - Exam-mode non-goal corrected. ExamExerciseDetailView gets its own untracked overlay mount; tracking is regular-exercise-detail only. - closeIdentity snapshot now includes participationId/resultId so the snapshot pattern works for the new schema. - Lifecycle flow text aligned with provider-events pattern; no longer says command handlers call the recorder directly. * docs(spec): apply codex round-3 cleanup nits - Architecture diagram onClose union now matches detailed spec (3 user reasons; 'replaced' is parent-emitted, not overlay-emitted). - Activation file named explicitly (extension.ts:100) rather than 'or wherever'. - Note that the 4 new EventEmitters must be disposed with the provider. - Tighten command-handler test description: getPayload only catches the missing-payload case, does not shape-validate. * docs(plan): test-results tracking and per-task feedback modal 20-task TDD implementation plan for the spec at docs/superpowers/specs/2026-05-12-test-results-tracking-design.md. * docs: revise spec + plan per codex review of plan (round 1) Spec changes: - Drop 'backdrop' from closeReason union. Current full-screen modal layout has no clickable backdrop area; only X-button, Escape, and 'replaced' (synthetic on modal switch) remain. Plan changes: - Task 8: subscribe directly via artemisWebviewProvider from RecorderWiringDeps (no providerRegistry indirection). Extend the existing sessionRecorderWiring.test.ts with 4 forwarding tests. - Tasks 15 + 16 (separate SubmissionStatus and ExamExerciseDetailView commits) merged into Task 17 (now: Migrate overlay ownership and wire ExerciseDetailView tracking) — single atomic commit avoids the intermediate broken-typecheck state. - Task 17: use real variable names (participation, participationId), store testIds + derive filtered list live so the modal updates with new build results (matches spec). Real test for the 'replaced' flow via DOM-simulated task click (replaces placeholder). - Task 17 fixture: results live on submission.results (matches the component's lookup path), not participation.results. - Task 16: CSS uses :global(.artemis-task) so the literal SSR class is targeted under CSS modules. - Task 3: add contract test for COMMANDS_REQUIRING_PAYLOAD membership (spec-required, was missing). * docs: drop 'replaced' closeReason; tighten plan per codex review of plan (round 2) Spec changes: - closeReason union reduced to 'button' | 'escape' (no 'replaced'). Rationale: the TestResultsOverlay is full-screen, which covers all in-UI triggers for the other modal. The user cannot reach a switch affordance while a modal is open, so the 'replaced' transition is unreachable. Documented this reasoning in the spec. - OpenTaskView state now stores testIds (not snapshotted 'filtered'), matching the live-derivation pattern the plan uses. Plan changes: - Drop 'replaced' tests and the defensive 'if (openOther) handleOtherClose' branches in handlers — both are dead code paths now. - Task 8 wiring tests rewritten to use the real makeWiringHarness + stubWebviewProvider pattern; harness now exposes artemisWebviewProvider so tests can fire its events. - Task 15 ProblemStatement test snippet no longer duplicates existing imports; only adds 'userEvent' and appends the new describe block. - Final-verification counts replaced with approximate per-file deltas (the prior exact numbers became wrong after Task 3 and Task 8 added more tests). * docs: address codex round-3 coverage gaps (test-results tracking plan) - Task 2: add positive test for recordTaskFeedbackClosed (closeReason='escape') alongside the existing closed-event tests. - Task 9: add three new positive tests for testResultsOverviewClosed, taskFeedbackOpened, taskFeedbackClosed command handlers (only TestResultsOverviewOpened was being asserted). - Task 17: re-add the task-click React test that was lost when 'replaced' was dropped. New test dispatches ProblemStatementRendered, clicks the rendered .artemis-task[data-test-ids] span, asserts taskFeedbackOpened payload (taskName, testIds, counts, viewId). - Spec: remove the manual test step describing the impossible task-modal-then-See-test-results switch (full-screen overlay blocks it, matches the round-2 reasoning at spec line 96). * docs(plan): fix task-count nit in final verification (2 not 1 ExerciseDetailView tests) * feat(recorder): add TestResultsOverviewView and TaskFeedbackView event types * feat(recorder): add public API for test-results and task-feedback view events * feat(messages): add webview commands for test-results view tracking * feat(provider): add IArtemisWebviewProvider interface for view-tracking events * feat(provider-registry): expose ArtemisWebviewProvider via registry * feat(provider): emit view-tracking events from ArtemisWebviewProvider * feat(activation): register ArtemisWebviewProvider in provider registry * feat(wiring): subscribe to view-tracking events and forward to recorder * feat(commands): add TestResultsTrackingCommandModule * feat(commands): register TestResultsTrackingCommandModule * feat(webview): add makeViewId helper for view-event pairing * feat(testcase): carry testCase.id through to UI test cases for per-task filtering * feat(webview): add filterTestCasesByIds helper * feat(overlay): add taskName prop and typed onClose(reason) to TestResultsOverlay * feat(problem-statement): emit onTaskClick when a [task] span is clicked * style(problem-statement): pointer cursor and underline on clickable task spans * feat(exercise-detail): migrate overlay ownership and wire test-results view tracking - Remove TestResultsOverlay mount from SubmissionStatus; replace toggle prop with onOpenTestResults callback. - Exam view (ExamExerciseDetailView) now mounts its own untracked overlay. - ExerciseDetailView owns both overlay instances (overview + per-task), generates viewId per open, computes durationMs at close, and forwards the four new tracking commands. Live re-derivation of filtered tests from current testCases lets the modal update across build-result events. - Update test fixture so result feedbacks live on the submission (matching the real component's lookup path). - Update SubmissionStatus tests to reflect renamed prop (onOpenTestResults). * refactor: hoist OpenViewState types to module scope; drop unused export - Move OpenViewState/OpenTaskViewState interface declarations out of the ExerciseDetailView function body to module scope. They were not visible to tests or sibling files; hoisting makes them importable and removes the visual noise inside the component. - TestResultsOverlayCloseReason is internal-only (no external imports); remove the unused 'export' keyword (knip flagged it). * style(problem-statement): preserve default task style, hover-only affordance Remove always-on underline on clickable tasks; rely on cursor + hover color shift (vscode-textLink-activeForeground) so the resting state matches the Artemis SSR default exactly.
The hand-rolled thumbs-down SVG path was malformed and rendered as a filled blob when selected. Swapped both feedback buttons to the Lucide ThumbsUp/ThumbsDown icons already imported elsewhere via iconMap.
…151) Swapped ~25 hand-rolled <svg> icons across BackLink, ServiceHealth, TextInput, IrisChatView, ChatInput, CodeBlock, ContextSelector, ReferencedFiles, and ExamConductionView for the matching lucide-react components (Send, Menu, Info, Eye/EyeOff, ChevronLeft/Down/Right, RefreshCw, File/FileText, BookOpen, MessageSquare, Check, XCircle, Search, Circle, ExternalLink, Copy). Removes a class of bugs (the recent thumbs-down filled-blob issue came from a malformed inline path) and aligns with the existing iconMap tree-shaking pattern. All descendant CSS selectors keep working since Lucide renders <svg> with currentColor strokes. 138 vitest tests pass, tsc + eslint clean.
The provider's createNewSession redirected to the workspace exercise whenever a workspace exercise was detected and the active context was something else, swallowing the click instead of creating a new session. The dedicated "Chat about Workspace Exercise/Course" buttons in the context selector already cover that switch, so the redirect is dead UX. Delegate straight to the session service.
Planning specs and plans for intervention-ui-toggle, test-results-tracking, and iris-api-unified-sessions are no longer load-bearing — the features they describe have all shipped. Drop them so the repo stops carrying historical planning snapshots that nothing else references.
…nd EQ snapshot intake (#152) Four thesis-evaluation-relevant correctness fixes in the struggle-detection pipeline. All four would have biased recorded metrics or distorted UI delivery behaviour during a study run. #1 Adaptive cadence now only increments on explicit 'user-action' dismissals. Implicit lifecycle dismissals ('replaced', 'hidden', 'session-end') no longer skew the per-trigger ignore counters that drive threshold escalation. #2 Dismiss events without a triggerType are skipped with a logged warning instead of falling back to 'idle' (which polluted that bucket). #1b InterventionState.lastDismissed is no longer flipped by implicit dismissals. Previously, a subtle-hint replace or a build-success hide would suppress the next subtle/notification delivery via InterventionFilter, even though the user never dismissed anything. _emitDismiss is now pure event emission; the user-action paths set the flag explicitly. #5 Build results no longer take an inline snapshot path next to the onDidEmitCompileEquivalent listener. Both save and build snapshots flow through a single listener that propagates event.source, eliminating the fragile source-based filter. handleBuildResult returns void. Tests: 794 unit / 135 struggle / 0 failures.
…ndpoints (#153) #3 getOrCreateVcsAccessToken no longer falls back to PUT on every error. Empirically verified against the Artemis backend (AccountResource.java + UserTestService.java): 'no token yet' surfaces as 404. Only that status triggers the create-token PUT now; 401/403/5xx and network errors propagate so the caller does not pile a second failure on top of an already-broken auth or server state. #4 getLatestPendingSubmission no longer maps every error to null. Policy: 404 or empty/null body → null; 401/403/5xx → throw; malformed non-empty JSON → throw MalformedResponseError with endpoint context. Caller in exerciseDataLoader was updated with a matching enrichment- error policy (401/403 and MalformedResponseError rethrow; transport failures log a warning and the exercise page still loads). Additional hardening: - New MalformedResponseError subclass of ApiError so callers can distinguish schema/contract failures from transport failures cleanly (replaces the prior stringly-typed message check). - getLatestResultWithFeedbacks now wraps JSON parse failures in MalformedResponseError so the loader policy applies uniformly. - parseProgrammingSubmission now rejects arrays and requires a real finite numeric id (was tolerating null/false/'' via Number() coercion). Tests: 815 unit / 135 struggle / 0 failures. Plan and code reviewed by codex; all blockers resolved before commit.
While the first connect() awaits _client.deactivate(), a parallel connect()
call observes state==='connecting' at the top of the method and returns the
existing _connectDeferred.promise. If the catch path then fired (deactivate
failed, auth headers empty), the old code dropped the deferred without
settling it, and every concurrent awaiter hung forever.
The catch now routes through _settleDeferred(error). To keep the
single-caller catch path clean (no external awaiter on the internally-created
deferred), _settleDeferred attaches a defensive .catch() to the deferred's
promise before rejecting it. External awaiters still observe the rejection
via their own await; Node no longer reports an unhandled rejection when no
external awaiter exists.
Tests:
- concurrent-awaiter scenario: deactivate held open by a gate so a second
connect() attaches to the same deferred; both awaiters must reject with
the underlying error.
- single-caller scenario: process.on('unhandledRejection') hook asserts
no rejection escapes the new catch path.
…-command bandaid (#155) Pre-existing code wrapped `vscode.commands.registerCommand` for the subtle- accept command in a try/catch that silently swallowed VS Code's "command already exists" error. That kept tests passing across multiple TelemetryManager instances but routed status-bar clicks at runtime to a stale handler whenever the lifecycle accidentally double-registered. Production lifecycle: - extension.ts:deactivate now calls `activeTelemetryManager.dispose()` explicitly (was `endCurrentSession()`), wrapped in try/catch with a logged error. The dispose path tears down InterventionService's command/status-bar disposables; the old session-end-only path left them registered for the rest of the host process. - TelemetryManager.dispose() is now idempotent (early return on `_disposed`). VS Code disposes `context.subscriptions` after our explicit deactivate call, so the second teardown must be a safe no-op. - InterventionService no longer wraps registerCommand in a try/catch. Duplicate registration fails loudly now. Tests: - Every suite that constructs InterventionService or TelemetryManager now stubs `vscode.commands.registerCommand` via a sinon sandbox so it does not collide with the extension's own activate()'d instance. `sandbox.restore()` in teardown guarantees cleanup even if a test throws mid-flight. - Added a small TelemetryManager.dispose() idempotency test. 818 unit / 135 struggle / 0 fail.
…ord* wrappers (#156) All fifteen public `record*` methods (chat / view / EQ / lifecycle events) had identical boilerplate: phase guard, `Date.now()` timestamp, empty `opts`, current-generation capture, then delegate to `LifecycleController.recordInternal`. About 200 LOC of repetition that hid small accidental divergences across methods. Centralised the pattern in `_record(event)`. Public methods become declarative payload definitions. Narrow on purpose: - `onNewResult` and any collector-based path (which carries its own timestamp / opts) still calls `_lifecycle.recordInternal` directly. - Generation is captured synchronously inside `_record`, identical to the previous per-method behaviour. `RecordedEvent` is a discriminated union and the built-in `Omit` collapses it; a distributive omit `RecordedEventWithoutTimestamp` keeps each variant intact. `timestamp` is spread first so the serialised JSON line preserves its pre-refactor byte-level field order. 818 unit / 135 struggle / 3 recorder-e2e — all green.
…157) The single 110-LOC command handler had four concerns in line: collect diagnostics, build the status report, decide the action set, dispatch the user's choice. It also swallowed every auth-header failure silently and duplicated the action labels between the build site and the handler switch. Extracted: - `collectWebSocketStatus(ws, auth)` → `WSStatusSnapshot` - `buildStatusReport(status)` → markdown string - `decideStatusActions(status)` → `WSStatusAction[]` - `buildStatusHeadline(status)` → one-line notification headline - `handleStatusAction(action, ws, report)` → dispatcher `WS_STATUS_ACTION` is a const-tuple so the labels can no longer drift between build/handle sites. The previously silent catch on auth-header lookup now logs at `warn` — the surrounding code path runs only on explicit user request (artemis.checkWebSocketStatus), so visibility in the output channel during a diagnostics session is desired. Public API, UI/UX, and reconnect semantics unchanged. 818 unit / 0 fail.
The single 145-LOC `connect()` method covered six concerns: in-flight
reuse / state guards, generation bump, prior-client teardown, auth-header
validation, STOMP config assembly, and error settling. The race-sensitive
ordering (generation bumped before deactivate await, deferred created
before the try block, superseded-check after client assignment) was
implicit and easy to break in a future edit.
Extracted helpers:
- `_maybeReuseInflightConnect()` — early-return for in-flight or stale
connecting, throws for disconnecting / gave-up.
- `_teardownExistingClient()` — deactivate + clear subs + null the
client reference.
- `_prepareConnectionContext()` — server URL, auth headers, JWT
validation (pre-flight only — `connectHeaders` stays empty by
design), ws URL.
- `_buildStompConfig(generation, authHeaders, wsUrl)` — full StompConfig
with generation-checked lifecycle callbacks.
- `_handleConnectError(error)` — log + clearSubs + transition + settle
the deferred (the settle keeps PR-3's concurrent-awaiter fix intact).
`connect()` is now a 25-line coordinator. Critical ordering is preserved:
generation bump → deferred creation → try { teardown await → context →
build config → createClient } → superseded check → activate. Every STOMP
callback still captures the same generation snapshot via the closure
parameter.
No behavior change. 818 unit / 135 struggle / 0 fail.
The 155-LOC handler nested six concerns (payload validation, git pre-flight,
default-clone-path validation, three-button "set default / choose / don't
ask" modal, programmatic clone, post-clone cache update) inside a single
try/catch. The "Clone cancelled - …" cancellation notice appeared five
times with two slightly different wordings; the FIFO eviction at size 10
used a bare magic number with an "LRU" comment that was inaccurate.
Extracted:
- `_resolveCloneDestination(exerciseTitle)` — full path-resolution
decision tree. Returns undefined on user cancel; cancellation
messaging is a side effect.
- `_validateDefaultClonePath(p)` — returns `undefined` if usable, or a
short reason string for the warning toast.
- `_pickFolderOrCancelClone(label, title)` — folder picker + the
standard cancellation notice. Replaces five near-identical duplicates.
- `_rememberClonedRepo(participationId, repoPath, title)` — FIFO
eviction at `MAX_CLONED_REPO_CACHE_SIZE`. Comment now spells out
FIFO-not-LRU, matching the actual insertion-order semantics.
`handleCloneRepository` itself is now ~40 lines: payload check → git
availability → resolveCloneDestination → buildAuthenticatedUrl →
programmatic clone → rememberClonedRepo → notice → Open-Folder prompt.
Minor copy-cleanup: the two slightly different cancellation wordings
("no folder selected." / "no destination selected.") are unified to
"no folder selected." Behavior, flow, modal buttons, picker labels, and
default-clone-path persistence are otherwise preserved.
Magic number 10 → `MAX_CLONED_REPO_CACHE_SIZE` constant.
818 unit / 0 fail.
Replaces ten silent 'as Promise<T>' / 'as unknown[]' casts in artemisApi.ts
with parser calls so an unexpected response shape from Artemis surfaces
as `MalformedResponseError` (subclass of `ApiError`) instead of garbage
typed-as-T flowing into the UI. Builds on the per-endpoint policy
established in PR-2 (exerciseDataLoader rethrows
`instanceof MalformedResponseError`).
New helper module `extension/src/extension/domain/responseValidation.ts`:
- `expectObject(label, data)` — non-array object check.
- `expectArray<T>(label, data, elementValidator?)` — array check + optional
per-element validator.
- `parseApiObject<T>(label, data, required?)` — object check + listed
required primitive-typed fields.
All three throw `MalformedResponseError` so callers can keep using
`instanceof MalformedResponseError` to distinguish schema failures from
transport failures.
Endpoints wired up:
- getArchivedCourses → expectArray + per-element expectObject
- getCoursesForDashboard, getCourseForDashboard,
getExerciseDetails → parseApiObject (shape only)
- getIrisCourseChatSettings → parseApiObject
- getChatMessages → expectArray + per-element expectObject
- sendChatMessage (primary + fallback) → parseApiObject
- getCurrentChat, createChatSession → parseApiObject with required
{id: number}
- listChatSessionsForCourse → expectArray + per-element parseApiObject
with required {id, entityId, creationDate, mode}
- getExamSidebarData → expectArray + per-element expectObject
- getOwnStudentExam, startStudentExam → parseApiObject
- getBuildLogs → simplified to expectArray(parseBuildLogEntry)
- getLatestResultWithFeedbacks → parses to `unknown` first, then
parseApiObject<ResultSummary> (was silently casting any non-empty
JSON to ResultSummary, e.g. `[]`, `42`, `"ok"`)
Two remaining `as unknown` casts in renderProblemStatement live inside
the pre-existing `isRenderedProblemStatementDTO` typed-guard path and
stay as is.
Tests: 837 unit / 0 fail / 3 skip. Twelve helper-coverage tests plus
five integration tests on representative endpoints.
…161) Synced four recording event types that the extension already emits but the viewer had no handling for (configurationSnapshot, configurationChange, testResultsOverviewView, taskFeedbackView). Each gets a marker color, a badge CSS pair, EventStream + TrackingTimeline detail rendering, and a legend entry in RecordingInfo. The constants.ts exhaustiveness check now covers them so future stale syncs fail at compile time.
) Deletes code that exists but does nothing, to make the extension easier for a new developer to follow. All changes are behavior-preserving except the removal of ThrashingDetector, an unused developer-mode debug metric that fed no decision, EQ input, or recording event. - AuthManager: drop unused getStoredTokenValue, simplify getStoredToken - Delete the dead hasRecentlyClonedRepo chain (no callers) - LoggingService: drop unused viewWarn - IrisWebSocketSessionClient: drop write-only _isSubscribed field - Delete ThrashingDetector (163 LOC; hashed the whole document on every keystroke for two debug-output lines) - RecorderLifecycleState: drop the dead EventEmitter — the live state-change path is LifecycleController's plain callback - BuildResultTracker: drop the misleading WebSocketMessageHandler interface and an always-empty disposables array - Drop the unused messages parameter from createSessionWithDetails - Delete ViewActionService, a pure pass-through layer; inline its three methods into ArtemisWebviewProvider (error boundaries preserved) - SessionRecorder: remove a misleading byte-layout comment
…#164) * refactor(extension): introduce @-path aliases for cross-layer imports Replaces deep relative path traversals (../../../../shared/...) with readable @-aliases so a new developer can tell at a glance which layer an import crosses into. - tsconfig.json: add baseUrl + paths for @extension, @webview, @shared - compile-tests / watch-tests: run tsc-alias so the out/ test build resolves aliases to relative paths (tsc does not rewrite them) - esbuild.js: set explicit tsconfig on both build contexts - vitest.config.mts: mirror the three aliases in resolve.alias - eslint.config.mjs: add layer-boundary rules — webview must not import @extension, extension must not import @webview, shared must not import either (prevents cross-layer leakage the aliases would otherwise allow) - rewrite 653 imports across 254 files: cross-layer and deep same-layer imports become aliases; same-directory / single-level siblings stay relative; JSON and repo-root imports are left untouched Behavior-preserving — module resolution only. Verified: check-types, lint, knip, esbuild bundle (no leftover @-specifiers in dist/ or out/), unit 835, struggle 135, react 873 all green. * fix(extension): pin tsc-alias in lockfile, extend boundary lint to entry - package-lock.json: reconcile the direct tsc-alias spec to the exact 1.8.17 pin (was still ^1.8.17) - eslint.config.mjs: include src/extension.ts in the extension-host layer-boundary block so the entry file is guarded too
* refactor(extension): sort and group imports consistently Adds eslint-plugin-simple-import-sort so import order is enforced and auto-fixable instead of ad-hoc. Imports are grouped, in order: 1. side-effect imports (CSS etc.) — kept first, relative order preserved 2. external packages (node builtins, vscode, npm) 3. @shared/* layer 4. @extension/* and @webview/* layers 5. relative imports simple-import-sort reorders named members but does not format them, so the ESLint core comma-spacing rule is enabled to normalise the separators it leaves behind. Applied repo-wide via eslint --fix. Behavior-preserving — import order only. Verified: check-types, lint, esbuild bundle, unit 835, struggle 135, react 873 all green; webview side-effect CSS order (base.css before katex) preserved. * refactor(extension): normalise multi-line import member layout simple-import-sort reorders named members but does not reformat them, which left 62 multi-line imports with inconsistent indentation and brace placement. A codemod rewrites every named import/export to a single line, or — past 120 chars — to a clean one-member-per-line block. Member order (set by simple-import-sort) is unchanged. Verified: check-types, lint, esbuild, unit 835 / struggle 135 / react 873 all green.
* refactor(extension): remove exam mode entirely Exam mode (exam start, conduction, exam-exercise detail, exam timer) is out of scope for the extension's core purpose and carried a large amount of code, state, types, and tests. Remove it in full. - Delete the dedicated exam views, components, hooks, worker, utils, and stores, plus examErrorHandler and all exam-only tests. - Strip exam entries from the shared message contracts (ExtensionMsg, WebviewCmd, COMMANDS_REQUIRING_PAYLOAD), domain types, and apiResponses. - Remove the exam payload variants from AppStateManager's navigation union; currentExerciseData now returns ExerciseDetailsResponse only, which drops several `as` casts at the call sites. - toCourseDetailData now explicitly omits server-provided exam data so no exam payload reaches the webview. - Drop the exam exercise branches in ParticipationActions. - Update the developer docs and architecture diagrams. Closes #172. Also closes #169 (the unreachable-key bug lived in the deleted examErrorHandler) and removes two cast sites tracked in #171. * refactor(extension): remove dead code orphaned by exam-mode removal After removing exam mode, several components and a hook were left with no remaining consumers (their only callers were the deleted exam views): - BuildProgress component (+ CSS + test) — only rendered by ExamExerciseDetail - TestResults component (+ CSS + test) — only rendered by ExamExerciseDetail - useRelativeTime hook — only used by ExamStart - ExerciseDetail/types.ts: TestResultsProps + TestCase (used only by TestResults) - domainTypes.ts: stale ExerciseDetail re-export (was for exam navigation) - dompurify / @types/dompurify direct dependency — DOMPurify was only used by ExamStart; still present transitively via mermaid Verified with knip (zero unused files/exports/dependencies), tsc, eslint, 716 react tests and 835 unit tests passing.
The Iris WebSocket only delivers complete MESSAGE / STATUS frames, never
chunked content. The store's appendStreamChunk / finishStreaming actions
were therefore unreachable, and the same was true for setMessages,
setMessageStatus, and the streamingChunks / visibleChunks read side that
fed StreamingMessage.
Removes all unreachable streaming infrastructure (4 store actions,
StreamingMessage component + CSS module, streaming props on MessageBubble,
visibleChunks reads in ChatMessageList) and their tests. StreamingState
is reduced to { isStreaming: boolean }, startStreaming() takes no args,
and the thinking-indicator path is preserved (driven by resetTransientChatUi
on assistant AddMessage, as in production today).
ChatMessage.status / errorMessage and the MessageBubble error UI are kept
deliberately. The error UI still reads them, and the missing write path
on send rejection is a separate concern.
…178) (#180) * fix(webview): clear stuck thinking indicator on Iris send rejection (#178) When the extension host rejected a send synchronously (no-context, no-ai, iris-disabled), the webview was never told. The thinking indicator stayed on forever, the optimistic user message was orphaned, and for no-context the only feedback was a VS Code toast. This change introduces an explicit SendRejected message from host to webview, scoped by localId + localSessionId so stale rejections cannot clobber a fresh request after the user switched session. UI / UX - Failed user messages now keep their original content visible and gain a "Not sent" footer with the rejection reason and a Retry button. - Retry is gated per reason: iris-disabled is permanently unretryable, no-ai stays disabled while .noai is still detected, no-context stays disabled while no context is selected. - ChatInput is now disabled while a send is in flight (prevents racing sends from each other's transient UI). - no-context drops the VS Code toast; the inline error is sufficient. Wire protocol - sendMessage command gains localId + localSessionId. - New SendRejected extension message: { localId, localSessionId, reason, errorMessage }. - markMessageFailed only matches role==='user' && status==='sending' and returns whether it matched, so we never reset transient UI for a rejection that didn't correspond to a pending message. - Backward fallback: if the host receives a sendMessage without correlation IDs, it falls back to an assistant-style AddMessage so the existing reset-on-AddMessage path still self-heals the indicator. Tests - useChatStore: markMessageFailed (matched / non-matched / wrong-role / wrong-status) and removeMessage. - MessageBubble: Retry click, Retry disabled, original content preserved on error, no feedback buttons on failed messages. - irisChat.flow: full rejection round-trip, stale rejection ignored, Retry removes+resends with fresh localId, ChatInput disabled during send, Retry disabled when reason still holds. Out of scope: the catch-block error path in _handleChatMessage (covers post-acceptance transport/backend failures, different UX model). Closes #178 * test(webview): direct coverage for racing-second-send and retry-of-retry Addresses codex review feedback on PR #180. The previous flow tests proved the ChatInput becomes disabled and that a single retry produces a fresh localId, but did not directly exercise: - a second user-initiated Enter while streaming is true (verifying exactly one sendMessage command lands on the wire), - a retry of an already-retried message followed by a second rejection (verifying the retried entry can itself be marked failed and that thinking clears again). Both cases now have dedicated assertions in irisChat.flow.test.tsx.
) * fix(extension): scope pending submissions per participation (#168) The exerciseDataLoader's per-participation loop assigned every participation's pending submission onto a single shared `exerciseDetails.pendingSubmission` field, so only the last participation's pending build survived. The webview then risked showing a stale or wrong "build in progress" state when the user was viewing a different participation (e.g. practice repo while the graded repo had the surviving entry). Data shape change - New shared DTO `PendingSubmissionStatus` in apiResponses.ts. The `ExerciseDetailsResponse` carries `pendingSubmissionsByParticipationId: Record<number, PendingSubmissionStatus>` instead of the old singleton. - Webview store mirrors the map. WebSocket-driven updates (`updateSubmissionProcessing`, `updateBuildStatus`) mutate per participationId — entries for other participations are preserved. - `updateBuildStatus` falls back to `findParticipationForResult` when the payload omits `participationId`, so stale pending state cannot remain for result events lacking that field. - ExerciseDetailView picks the entry matching the currently-selected participation (`participation?.id !== undefined ? map[id] : null`). Normalization `api.getLatestPendingSubmission` returns a `ProgrammingSubmission` (no top-level `state`, no `participationId`). The loader normalizes that into a bare `{ participationId }` entry — state and `buildTimingInfo` only ever arrive from the WebSocket submissionProcessing path. The UI is already tolerant of undefined `state` and chains `buildTimingInfo?`. Parallelization Per-participation enrichment now runs in parallel via `Promise.allSettled`. Auth (401/403) and `MalformedResponseError` are collected and rethrown after every per-participation task has settled, matching the previous abort-on-fatal semantics while preventing background log spam from sibling calls after rejection. Non-fatal failures (network, 5xx) are logged and the load continues with whatever did succeed. Concurrency cap intentionally omitted: typical participation count is ≤2. Tests - Loader (test/unit): two-participation co-existence, normalization shape, one-failing-doesn't-drop-the-other, auth-after-settle, empty-map for no pending builds. - Store (test/react): hydration from response, stale-reset on reload without map, preservation across `updateSubmissionProcessing`, per-participation deletion via `updateBuildStatus` (with and without `payload.participationId`), `clearPendingSubmission` clears all. - Fixtures in exerciseSubmission.flow and ExerciseDetailView migrated to the new shape. Backward compatibility: extension host and webview ship in the same VSIX, so the wire-shape change has no version-skew surface. Closes #168 * fix(webview): use resolved participation id when clearing pending entry Addresses codex review feedback on PR #181. `updateBuildStatus` previously prefered `payload.participationId` when deciding which key to delete from `pendingSubmissionsByParticipationId`, falling back to the resolved participation only when the payload omitted it. A mismatched or stale payload (carrying an id that points to a different participation than the one the result tree resolves to) would have deleted an unrelated key while mutating the actually-resolved participation. Always use the resolved owner instead. Also adds the view-level regression test codex flagged as the main coverage gap: two participations with concurrent map state, only one holding a pending build, and the view must surface the indicator only when its selected participation is the one with the pending entry. Switching `isPracticeRepo` flips which participation is selected and thus whether the indicator renders — proving the pre-fix data leak is gone.
…uards (#186) (#187) - utilityCommands.ts: drop redundant 'as ExerciseDetailsResponse | undefined'. AppStateManager.currentExerciseData already returns exactly that type via the discriminated _payload.kind === 'exercise' check; the cast was stale leftover from before the appStateManager refactor. - iconMap.ts: introduce isIconKey() type guard using hasOwnProperty (not 'in') so inherited names like 'constructor' or 'toString' cannot slip through and return a non-LucideIcon at runtime. Removes 'as IconKey'. - ParticipationActions.tsx: promote ExerciseType union to const-array + derived type + isExerciseType() guard. The hand-written union is now the single source of truth and the guard rejects unknown server values instead of blindly casting them in. ExerciseDetailView consumes the guard to replace its '(exercise.type || 'programming') as ExerciseType' call site. - extensionCommands.ts: introduce isWSStatusAction() guard derived from Object.values(WS_STATUS_ACTION). The vscode.window.showInformationMessage return type loses the WSStatusAction[] info from the items spread, so the prior cast was unsound when the user could in principle pick any string. The guard is now the single narrowing point. Closes #186.
…ssionRecorder (#184) (#188) SessionRecorder had two private getter shims that double-cast through ObservationRegistry's private fields purely to satisfy sessionRecorder.test.ts which whiteboxes the per-URI debounce maps. Production code violating encapsulation only to keep tests happy is a code smell — the test was dictating the production shape. Move the whitebox from production to the test: - src/extension/services/telemetry/recording/sessionRecorder.ts: Remove _pendingSelectionPayloads and _pendingVisibleRangePayloads forwarding getters and their double-cast. - test/unit/services/telemetry/recording/sessionRecorder.test.ts: Add a single pendingDebounceMaps(recorder) whitebox helper that centralizes the unsafe access through _observation. All 13 test sites (consent-downgrade discard, Block J per-URI flush/discard, visible-range alternation) now go through the helper instead of hitting (recorder as any)._pendingSelectionPayloads directly. The cast uses 'as unknown as' rather than 'as any' to be explicit about the whitebox. Also fix two stale comments while nearby: - The trigger-time payload test referenced _flushPendingDebouncesForEnd but the code calls endSession(); reworded for clarity. - The consent-downgrade test mentioned 'via as any' which no longer matches the helper-routed access. No semantic change to the tests — the underlying Map instances are identical to what the production shim used to forward. Unit tests 835/0 failures, react tests 720/720. Closes #184.
…ecordedEvent narrowing (#185) (#189) RecordedEvent is already a properly tagged discriminated union over 'type' (recording/types.ts:389-422 — 33 variants, each with a literal type discriminator). TypeScript therefore narrows event automatically after every 'event.type === <literal>' check, so the seven explicit casts in replayEngine.ts were redundant from day one. - Drop all seven 'as ConcreteEvent' assertions. - Drop the five intermediate 'const xEvent = event as ...' aliases that existed only to host the casts (saveEvent, buildEvent, diagEvent, stateEvent — the body now just uses 'event' directly). - Remove the five now-unused type imports (BuildResultEvent, DiagnosticsEvent, EqSnapshotEvent, SaveEvent, SessionStartEvent). EqEngineStateEvent, RecordedEvent and SerializedDiagnostic remain as they are still used as parameter / array types. No behavioral change. Verified via type-check, lint, and the full replayEngine.test.ts suite (part of the 720/720 react tests passing). Closes #185.
#207) (#217) * refactor(websocket): extract pure helpers (buildWebSocketUrl, extractJwtFromHeaders) Pulls two no-op private helpers off ArtemisWebsocketService into pure modules with their own unit tests. No behavior change. Part of #207. * refactor(websocket): extract buildStompConfig as a pure function Moves the STOMP config builder out of ArtemisWebsocketService into a standalone module with its own tests. Generation gating is now a pure closure capture, identical to the previous behavior. No behavior change. Part of #207. * refactor(websocket): extract SubscriptionRegistry from ArtemisWebsocketService Pulls the subscriptions Map, message-handler list, and per-topic helpers (personal results/submissions/processing + iris) into a dedicated class. The service attaches/detaches the STOMP client at connect/disconnect time. No behavior change. Part of #207. * refactor(websocket): extract ConnectionLifecycle from ArtemisWebsocketService Pulls the state machine, generation token, reconnect counter, deferred lifecycle, safety timeout, and debounce timer into ConnectionLifecycle. The service is now a thin orchestrator that owns the STOMP client and SubscriptionRegistry, routing STOMP callbacks through the lifecycle. Race-handling behavior is preserved byte-for-byte. Verified by the existing 60+ behavior tests plus 18 new lifecycle-specific tests. Closes #207. * docs(websocket): correct attach-order comment in connect() Said 'after recordConnected' but the code attaches before. Aligning the short comment with the long docstring on _onStompConnected. Part of #207.
…#220) * fix(iris-chat): distinguish unavailable from disabled, add retry path Network drops, 5xx responses, and timeouts previously surfaced the "Iris chat is not enabled for this exercise. Please contact your instructor." overlay, misleading students into thinking their instructor disabled Iris. After such a failure, the webview also stayed on "Loading conversation…" forever — no auto-retry on websocket reconnect, no manual retry button. checkAndLoadIrisSettings now returns a discriminated union (enabled/disabled/unavailable). A 403 from the iris-settings endpoint maps to disabled (matches existing semantics); 401/4xx/5xx/network/ malformed/timeout map to unavailable. 403 from other endpoints in the flow (profile probe, exercise-details course resolution) classifies as unavailable rather than as a disable signal. A new ShowUnavailableState message drives a transient warning banner with a Retry button. Manual retry posts ReloadChatSession; auto-retry fires on websocket reconnect when the current context's last classification was unavailable. Both share a context-keyed single-flight guard so a hung reload for one context cannot block recovery of another. lastAvailability is reset on every context change so a stale classification from a previous exercise cannot drive retries on the new one. The loader is now gated on availability state (disabledMessage or unavailableMessage), fixing the stuck-spinner symptom: availability failures are a terminal state, not a history-load failure. The websocket-disconnected banner is suppressed when the unavailable banner is shown so the user sees a single Retry affordance. The send path also flows through the centralized postAvailability helper, and a captured context is threaded through SendMessageResult so a slow settings check that returns after a context switch cannot mark the new context's banner state with the old context's classification. Closes #218 Closes #219 * docs(changelog): add iris-chat connectivity resilience entry
…221) * feat(context): add TrackedItemRepository.clearAllWorkspaceFlags * feat(context): add ContextStore.clearWorkspaceFlag and getWorkspaceExerciseId * feat(workspace): scaffold wireWorkspaceDetection with initial detection * feat(workspace): re-detect on workspace folder changes * feat(workspace): re-detect on courses-loaded events * feat(workspace): guard register path against stale detections * feat(workspace): guard clear path against stale detections * feat(workspace): dispose guard and listener unsubscribe * feat(chat): inject ContextStore + expose workspace sink methods * refactor(chat): remove chat-owned workspace detection * feat(activation): run workspace detection at activation * fix(recorder): rephrase exercise-context warning * fix(recorder): read workspace exercise from ContextStore * style(tests): fix import order and curly braces for workspace tests * docs(changelog): recorder gate decoupled from Iris chat context * chore: drop unused workspace exports flagged by knip
… re-PR (#222) * feat(context): add TrackedItemRepository.clearAllWorkspaceFlags * feat(context): add ContextStore.clearWorkspaceFlag and getWorkspaceExerciseId * feat(workspace): scaffold wireWorkspaceDetection with initial detection * feat(workspace): re-detect on workspace folder changes * feat(workspace): re-detect on courses-loaded events * feat(workspace): guard register path against stale detections * feat(workspace): guard clear path against stale detections * feat(workspace): dispose guard and listener unsubscribe * feat(chat): inject ContextStore + expose workspace sink methods * refactor(chat): remove chat-owned workspace detection * feat(activation): run workspace detection at activation * fix(recorder): rephrase exercise-context warning * fix(recorder): read workspace exercise from ContextStore * style(tests): fix import order and curly braces for workspace tests * docs(changelog): recorder gate decoupled from Iris chat context * chore: drop unused workspace exports flagged by knip
Lets the annotator flag moments where they believe the recorded student would benefit from Iris interaction. Added as a new ContextMarker value with hotkey `i` and indigo color, alongside existing markers like reading / using-ai. Also replaces the brittle positional `'qwert'[i]` lookup in the live legend with a reverse-map derived from CONTEXT_KEYS, so the legend stays in sync when context-marker hotkeys are non-contiguous.
Lets the annotator flag moments where the recorded student is reading test output (distinct from `reading`, which covers task description / docs). Added as a ContextMarker value with hotkey `u` and amber color, following the same pattern as `iris-moment` (#225).
#227) The live-session counter previously stuck at 5000 once the sliding window filled because it displayed the buffer length (`live.events.length`) rather than the cumulative count of events received. Two changes: - MAX_LIVE_EVENTS raised from 5_000 to 50_000. Sized to cover realistic study-session volumes (worst observed: ~4k events). RAM cost ~20 MB at the new cap; the real bound is re-derivation cost in EventStream / TrackingTimeline on each RAF flush. - LiveSessionState gains a `totalReceived` counter that increments per successfully-parsed, deduped event. LiveControlBar now takes (bufferSize, totalReceived) and shows '5,000 of 12,345 events' when the window is trimming, otherwise just '12,345 events'. Pre-cap dropped events still count toward totalReceived because they were received from the server, even if a pathological initial-tail burst kept only the visible tail.
When a WebSocket newResult event arrives for the currently rendered participation, re-fetch enriched exercise details and re-render the problem statement so test-case checkmarks reflect the new result. WebviewSSRCoordinator gains refreshFromServer with last-wins coalescing (at most one fetch in flight, one pending behind it), state/exerciseId guards, and disposed-aware scheduleRender. The filter lives in a pure function so it is unit-testable; today it narrows to the participation the coordinator renders (studentParticipations[0]). Visibility-change refresh gets the same post-fetch guard against stale overwrites.
#229) * feat(problem-statement): differentiated empty states for task feedback The per-task feedback modal previously collapsed every empty case ("no result yet", "build produced no feedback", "tests defined but not executed", "result is successful without an inline feedback list") into a single "No tests in this task." message. Port the four-state classification the Artemis web client uses (testStatusForTask) plus a legacy-success fallback and a defensive no-tests-in-task case into a pure classifyTaskTests function in exerciseStatus.ts. The TestResultsOverlay accepts the classified state as a discriminated prop and renders a tailored empty/structured layout per kind, including a "{N} tests in this task did not run" note when the latest build skipped some of the task's tests. The classifier runs in the overlay's JSX at render time so WebSocket newResult patches reflect in the open modal immediately. The telemetry payload gains an additive notExecutedTests field; totalTests / passedTests / failedTests keep their pre-existing matched-tests semantics so historical analytics are unaffected. The previously-unused filterTestCasesByIds helper is removed. * test: integration coverage for task overlay state classification Add six integration tests that drive the full click→classify→overlay render pipeline through every TaskTestState branch (no-result, no-feedbacks, legacy-success, not-executed, fail-with-not-executed), plus a freshness test that asserts the open modal re-classifies when exerciseData is patched mid-view (mirroring the WS newResult path). The existing isolated unit and component tests already covered the classifier and overlay separately; these add the missing end-to-end verification that the wiring in ExerciseDetailView dispatches each state to the right rendered output.
…nd IRR merge CLI (#231) * feat(viewer): add HMAC-signed ViewerSession cookie module * fix(viewer): harden cookieSign against torn input and add boundary tests * feat(viewer): add rater name normalization + opaque id derivation * test(viewer): tighten raterIdentity message + locale + UTF-16 assertions * feat(viewer): add per-filepath promise-chain mutex helper * feat(viewer): add append-only annotation event-log store with materializer * fix(viewer): tombstone only after materialized delete + validate records * feat(viewer): add researcher token + session secret config with startup validation * refactor(viewer): replace token-as-cookie with signed ViewerSession * feat(viewer): rewire /api/auth/login + /status for rater/researcher roles * refactor(viewer): role-aware auth gate with 405 on PUT and researcher read-only * feat(viewer): GET /annotations returns current rater's materialized marks * feat(viewer): POST /annotations appends to per-rater event log * feat(viewer): DELETE /annotations tombstones in own file, remove legacy PUT block * feat(viewer): add GET /annotations/all researcher endpoint with legacy synthetic lane * feat(viewer): login screen with rater/researcher mode toggle * test(viewer): delete obsolete legacy single-file annotation tests * feat(viewer): annotation controller blocks redo on rater identity mismatch * refactor(viewer): remove bulk-PUT annotation path and offline edit UI * feat(viewer): researcher mode renders multi-rater lanes * feat(viewer): add merge-annotations CLI (long-format CSV) * feat(viewer): add irr-matrix format to merge-annotations CLI * docs(viewer): document multi-rater study setup and merge CLI * fix(viewer): validate binMs is a positive integer in irr-matrix * fix(viewer): synthesize local rater session when no auth tokens configured * fix(viewer): enable annotation hotkeys for offline raters with playback cursor * fix(viewer): preserve lane raterName after tombstone + scope listRaterIds to r_*.jsonl * fix(viewer): render login mode toggle as side-by-side tabs The Rater/Researcher tabs inherited the full-width blue .login-form button style, so they stacked vertically and both looked active. Add dedicated .login-mode-toggle rules (flex row, only .active highlighted, hover state) and auto-focus the rater name field on the default tab. Also rename the dev convenience token to artemis_admin. * feat(viewer): prefill login from url params Read ?type=, ?name= and ?pw= query params to prefill the login mode, rater name, and access token. Lets a study operator launch several pre-configured rater/researcher windows without typing credentials each time. Fields are only prefilled, not auto-submitted. * feat(viewer): live-poll researcher lanes during a live session A researcher watching a live session only saw raters' marks on reload, because the live event stream carries events but not annotations. Add a useResearcherLanePolling hook that re-fetches /annotations/all once a second while a researcher views a live session and updates only the lane data, leaving video/zoom/scroll untouched. The poll reads the active session id per tick (so switching sessions re-targets it), uses an in-flight guard against overlap, and a per-run cancel flag so a poll resolving after the session ends cannot overwrite the authoritative final lanes written by the archive reload. * feat(viewer): mark live sessions in the session list The LIVE badge was appended after the session id inside an overflow:hidden cell, so it was clipped off the right edge and never visible. Move the badge to the front of the id cell, add a 'live' class to the row, and give live rows a subtle red tint. Adds a SessionList test covering the indicator and badge placement.
Records VS Code debugger activity in the session recorder and renders it in the recording-viewer: `debugSession` lifecycle (started / terminated / activeChanged) and `breakpointChange` events (added / removed / changed, each breakpoint carrying a stable id, absolute uri, 0-based line/column, enabled, and optional condition / hitCondition / logMessage), plus an initial breakpoint snapshot at recording-session start. Scope is limited to the high-level `vscode.debug.*` APIs (no DebugAdapterTracker, no struggle-detection integration). Adds runtime validators for the two new event types in `parseRecordedData.ts` and replaces the parser's switch dispatcher with a `satisfies Record<RecordedEvent['type'], EventParser>` table, so adding a future event type without a validator is a compile error (with an own-property guard preserving the unknown-type-to-null behaviour). Includes unit tests (collectors + phase/generation gating), a live breakpoint add/remove e2e, a wiring test for the startup-snapshot contributor, and viewer rendering plus render tests.
* feat(recorder): add submission event type, parser, and validator * feat(recorder): add recordSubmission stamping exerciseId from active session * feat(provider): add onDidSubmission event and fireSubmission * feat(submit): emit submission lifecycle events from handleSubmitExercise * feat(recorder): wire onDidSubmission to recordSubmission * feat(viewer): add submission to generated types and event constants * feat(viewer): render submission events in event stream and timeline * docs(changelog): note submission recording * feat(submit): cap recorded commit message and harden submission tests
…d timer (#237) * feat(viewer): distinct event colors, lane-label clipping, live elapsed timer Address three UI-polish items from #235: - Make MARKER_COLORS the single source of truth for event-type colors, consumed by both the canvas dots and a new shared <EventBadge> pill. Every type now has a distinct color (submission, startupPhaseComplete, debugSession and breakpointChange previously rendered colorless, and several types shared a hue). Remove the drift-prone per-type .event-badge.<type> CSS rules. - Clip the left timeline lane labels to their fixed-width column (ellipsis + title on hover) so long names no longer overflow to the left. - Show a running elapsed timer in the live control bar during live mode, derived from an authoritative start (metadata.startTime or the sessionStart event) and latched per session so it survives live-buffer trimming; hidden when no start is known. * feat(viewer): always show all event types, empty ones sorted to the bottom The event breakdown (top) and the timeline lane labels (left) now list every event type, including those with no events in the current session. Empty types keep the curated order but are sorted to the bottom and dimmed, via a shared orderTypesActiveFirst helper used by both views. Part of #235. * feat(viewer): add toggle to hide empty lanes in the timeline A button in the timeline controls switches between showing every event type as a lane (default) and showing only the lanes that have events. Part of #235. * ui(viewer): turn the hide-empty-lanes control into a slider toggle Replaces the text button (which wrapped awkwardly in the controls row) with a compact labeled switch. Part of #235. * feat(viewer): complete the 'What do we record?' panel with every event type Add the 7 missing event types (submission, debugSession, breakpointChange, terminalOpenClose, terminalCommand, consentChange, startupPhaseComplete) under two new categories (Debugging, Terminal). Their badge colors come from MARKER_COLORS via EventBadge, like every other badge, so they are correct automatically. The intro count is derived, so it now reads 36 across 9 categories. Move the panel data to recordingInfoData.ts (keeps RecordingInfo a component-only module for react-refresh) and add a test asserting the panel covers exactly ALL_EVENT_TYPES with a real color each, so it can't silently go stale again. Part of #235. * feat(viewer): open the live recording with Space from the session list New useOpenLiveOnSpace hook: on the session-list view (no session open), pressing Space opens the live recording — only when one exists, so it never hijacks Space otherwise. Ignores auto-repeat and focused interactive controls (inputs, buttons, links, etc.) so Space still activates a focused button. Opens the first session when several are live. Also reorder the timeline controls to [Hide-empty-lanes toggle] [Reset] [Follow] [+] [-] so the zoom +/- stay pinned to the right edge and don't shift when the conditional Reset/Follow buttons appear. Part of #235. * ui(viewer): show a 'press Space to open' hint while a live recording exists Makes the Space shortcut discoverable: a small banner with a red live dot appears above the session list only when at least one recording is live. Part of #235.
The recording-viewer subproject had no CI coverage: every job in this workflow inherits the top-level working-directory of extension, so typecheck, lint, test, knip, and build all run against extension only. The viewer's ~30 test files, its lint, and its build never gated a PR. Add viewer-lint, viewer-test, and viewer-build jobs that run in recording-viewer, and require them via the CI gate aggregator. The build job runs tsc -b && vite build, covering both type checking and bundling, so a separate typecheck job would only duplicate work.
The project requires exact-pinned dependencies (no ^/~); Renovate handles updates via PRs. The recording-viewer package.json still used caret/tilde ranges for 15 dependencies. Pin each to its currently-resolved version. Two specs move up to the already-installed patch: @vitejs/plugin-react 6.0.0 -> 6.0.1 and typescript-eslint 8.56.1 -> 8.57.0. No installed version changes; only the lockfile spec references are rewritten.
The video upload handler streamed the request body to a temp file via
fs.createWriteStream but never attached an 'error' listener. The stream's
lazy fd open is asynchronous, so on the magic-bytes reject path (which
calls ws.destroy()) the pending open could fail after the response was
sent — most reliably in tests, where afterEach removes the temp directory
before the open completes. The resulting ENOENT had no listener and
surfaced as an unhandled error that failed the whole test run, and it was
a latent process-crash risk in production (disk errors, etc.).
Attach ws.on('error'): tear down and, if nothing has responded yet,
return 500. Route all three exit points (reject, success, catch) through
a single-response 'respond' guard so the error path can never double-send.
Set rejected on stream error so later data/end events stop writing to the
destroyed stream, and mark abort paths settled so a late error can't reply
to a client that already disconnected.
Reproduced deterministically: the old pattern produced an unhandled ENOENT
on every iteration; with the listener, none.
Delete the unused FreeAnnotationForm component (no importers) and its orphaned CSS cluster (free-annotation-form, label-bar/divider/quick-btn, annotation-count, label-selected-badge, label-clear-btn, annotation-bar, free-annotate-btn, the scoped time-input rule), plus the orphaned chart-legend and chart-filter-bar/filter-bar/toggle-all rules and a duplicate EQ Chart section header. Each removed class was confirmed to have no live references; annotation-label and the base filter-btn are kept (still used). Drop the export keyword from four symbols that are only used within their own module: ANNOTATION_GROUP_PX, ANNOTATION_HIT_PX, MAX_LIVE_EVENTS, and applyEventLog.
EventStream and TrackingTimeline each carried a 34-case switch over the
same RecordedEvent union: EventStream's EventDetail (full detail row) and
TrackingTimeline's eventSummary (compact tooltip line). Adding an event
type or changing a field meant editing both in lock-step, and they had
already drifted (e.g. truncation lengths, terminalCommand duration).
Move both switches verbatim into one module, src/utils/eventDisplay.tsx,
exporting eventDetail(event) and eventSummary(event, sessionStartTime).
Each component imports and calls its function; the bodies are unchanged.
EventStream's call site goes from <EventDetail event={e}/> to
{eventDetail(e)} (the old EventDetail was a plain function component with
no hooks/state, so output and reconciliation are identical). Trim each
component's format imports to the helpers it still uses.
Now the event-type union is handled in exactly one place.
…223) dev already replaced the parser's switch with a typed EVENT_PARSERS dispatch table (satisfies Record<RecordedEvent['type'], EventParser>, added in #233). This change keeps that table and removes the remaining drift source flagged by #215: the validator's separately hand-synced KNOWN_EVENT_TYPES Set. - parseRecordedData.ts re-exports KNOWN_EVENT_TYPES, derived from the dispatch table's keys (typed ReadonlySet<string> so the validator can call .has() on an untrusted string read off disk without a cast). - validate-recording.ts imports that set via the @extension alias and drops its own hand-maintained list, so the two can no longer drift. - adds a unit regression suite guarding the shared set (#215). Closes #215.
The event-stream detail row (eventDetail) and the timeline tooltip (eventSummary) in eventDisplay.tsx had drifted on several fields after #242's verbatim split. Harmonize the accidental differences so the tooltip surfaces the same information as the detail row: - intervention: tooltip uppercases the action; detail now uses a pipe before EQ (was a space) so both read "ACTION | level | EQ". - eqEngineState: tooltip shows the confidence (detail already did, and eqSnapshot shows it in both views). - buildResult: tooltip appends "| N test(s) failed" on failures. - terminalCommand: tooltip shows duration "(Xs)" and the "[truncated]" flag. - submission: tooltip shows the commit message (truncated to 40 chars; the detail row keeps 60). - selectionChange: tooltip shows the column ("L<line>:<col>"). Intentional differences kept: the tooltip time prefix, styling, the text-vs-counts split in textChange, bp/breakpoint(s) wording, truncation lengths, and the eqEngineState field ordering. Adds eventDisplayDrift.test.tsx asserting both renderers surface each harmonized field, and omit the conditional ones when absent. Closes #243.
) * refactor(telemetry): move uriFilter out of recording/ to decouple EQ * refactor(telemetry): stop re-exporting recording from telemetry barrel * feat(dataCollection): add seam interface and no-op implementation * feat(dataCollection): full seam wiring consent, recorder and recording commands * build: add @datacollection path mapping and __IRIS_RECORDING__ global * refactor(activation): route consent + recorder through the dataCollection seam * refactor(webview): provide recording handlers via seam with fail-closed merge * refactor(activation): drop recording command registration (moved to seam) * style: sort imports after dataCollection seam refactor * build: add openvsx variant (host alias swap + webview define) * build(webview): gate recording controls behind __IRIS_RECORDING__ * build: add clean-manifest generator for openvsx variant * build: add staging-dir packaging for the openvsx clean VSIX * refactor(auth): stop re-exporting ConsentService from auth barrel * build: add metafile-based clean-bundle verifier * build: keep esbuild metafiles and packaging staging dir out of the VSIX * ci: build and verify the clean Open VSX variant * ci: publish clean variant to Open VSX and full variant to Marketplace * test(dataCollection): fix fullSeam ConsentService mock path after auth barrel decoupling * build: exclude reports/ from the VSIX * fix(test): make clean-bundle verifier test pass full lint (eslint src test)
…162) Theia auto-clone and git identity setup are now handled by the companion Scorpio extension (wired on startup on Scorpio main). Removes autoCloneIfNeeded/configureGitIdentityFromEnv and drops gitUri/gitUser/gitMail from TheiaEnvironment and the polled bridge vars. Keeps cloneRepositoryProgrammatic (user-initiated clone) and GIT_* in KNOWN_BRIDGE_KEYS for the diagnostic probe.
) Single 'Use workflow from' selector (drop the redundant ref input) with a main/dev-only guard. Plus edge-case hardening: idempotent GitHub Release (no --clobber, fail-closed asset post-check), single-sourced + validated changelog notes (reject empty/whitespace-only sections and duplicate headers before publish), Marketplace post-publish existence check, overwrite:true on all artifacts, pinned vsce on PATH in package-openvsx.js, and clearer Open VSX recovery guidance.
Bump extension to 0.4.5 and add the 0.4.5 CHANGELOG section (Added/Changed/Fixed/Removed/Internal).
| // Append BEFORE constructing the tailer is the analogue of the | ||
| // registry → first-poll race: cursor seeded at old size, but | ||
| // file has grown by the time we poll. | ||
| fs.appendFileSync(filePath, '{"between":1}\n'); |
| await t.pollOnce(); | ||
| expect(got).toEqual([]); | ||
| expect(t.currentLineNo()).toBe(2); | ||
| fs.appendFileSync(filePath, '{"new":1}\n'); |
| const stat = fs.statSync(eventsPath, { bigint: true }); | ||
| initialMtimeNs = stat.mtimeNs; | ||
| if (stat.size > 0n) { | ||
| const buf = fs.readFileSync(eventsPath); |
Comment on lines
+17
to
+18
| return html | ||
| .replace(/<script[^>]*katex[^>]*><\/script>/gi, '') |
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.
Sync all merged dev work into main ahead of the 0.4.5 release: Open VSX recorder-less build variant (#246), Theia auto-clone removed (now Scorpio, #162), release-workflow simplification + edge-case hardening (#247), 0.4.5 bump + changelog (#248), and the accumulated feature/refactor work since 0.4.4. Conflict-free; merged tree is identical to dev. Merge as a merge commit to preserve history (matching the previous sync).