Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
6baf24c
Added Capture to Main CodeChat
jspahn80134 Nov 9, 2025
77e52bd
Ongoing Development
jspahn80134 Dec 12, 2025
d3f6b54
Merged capture code with latest extension
jspahn80134 Dec 12, 2025
7f316c4
Modified capture test
jspahn80134 Dec 14, 2025
a70d0d7
Ongoing Development
jspahn80134 Dec 19, 2025
6b81ab3
Conflicts resolved
jspahn80134 Dec 19, 2025
ef4d6ed
Merge branch 'main' of https://github.com/jspahn80134/CodeChat_Editor
jspahn80134 Feb 6, 2026
d0607ac
Merge branch 'main' of https://github.com/jspahn80134/CodeChat_Editor
jspahn80134 Feb 13, 2026
3e02cee
Capture Changes
jspahn80134 Feb 13, 2026
8ad17f6
Capture Integration Updates
jspahn80134 Feb 14, 2026
822deba
Conflict resolution
jspahn80134 Apr 13, 2026
1edd6dd
Minor statrup fix
jspahn80134 Apr 13, 2026
fe4abbc
Code Review Workoff
jspahn80134 Apr 22, 2026
9fe5827
Merge upstream changes
jspahn80134 Apr 22, 2026
54cfac7
Fix capture lint failure
jspahn80134 Apr 22, 2026
63ca1c1
Update rustls-webpki for audit advisory
jspahn80134 Apr 22, 2026
e0f2b38
Fix VS Code extension lint
jspahn80134 Apr 22, 2026
a2c5e88
Ongoing development
jspahn80134 May 3, 2026
3db0ef3
Fix capture enum clippy warning
jspahn80134 May 3, 2026
bf6e60d
Add rich capture analysis dataset and schema
jspahn80134 May 8, 2026
c110546
Verify rich capture schema writes
jspahn80134 May 8, 2026
980b6eb
Stabilize WebDriver message timeout
jspahn80134 May 8, 2026
f6ccbca
Match WebDriver test wait to client timeout
jspahn80134 May 8, 2026
c7c885b
Serialize WebDriver integration tests
jspahn80134 May 8, 2026
6861965
Format WebDriver test lock
jspahn80134 May 8, 2026
8e7cf00
Address PR review feedback on capture extension
jspahn80134 May 11, 2026
6b1a068
Merge upstream main into PR branch
jspahn80134 May 11, 2026
f79285f
Address capture review cleanups
jspahn80134 May 13, 2026
9adb84d
Stabilize websocket test timeout
jspahn80134 May 13, 2026
f2759d2
Make capture event type an enum
jspahn80134 May 13, 2026
1364968
Clarify capture activity classification
jspahn80134 May 13, 2026
92144c2
Address capture review setup concerns
jspahn80134 May 14, 2026
585c405
Improve capture smoke diagnostics
jspahn80134 May 15, 2026
3c13067
Align capture settings state and audit events
jspahn80134 May 15, 2026
6b05869
Remove dissertation capture export utility
jspahn80134 May 17, 2026
e91683f
Stabilize doc block update test
jspahn80134 May 17, 2026
c2818c2
Address capture review cleanup
jspahn80134 May 18, 2026
5081913
Merge branch 'main' into main
jspahn80134 May 18, 2026
3b94efd
Refine capture event schema
jspahn80134 May 19, 2026
bee1be8
Capture code paste events
jspahn80134 May 22, 2026
7feed4e
Merge branch 'main' into main
jspahn80134 May 22, 2026
2827b9a
Stabilize client browser test
jspahn80134 May 22, 2026
f259b47
Capture non-paste external code insert candidates
jspahn80134 May 22, 2026
5ff5063
Address capture review documentation
jspahn80134 May 23, 2026
53c14f0
Use imported Path in translation
jspahn80134 May 23, 2026
7eace1a
Address remaining capture review comments
jspahn80134 May 23, 2026
4be3801
Address capture review follow-ups
jspahn80134 Jun 3, 2026
3fd74f2
Address capture review behavior
jspahn80134 Jun 4, 2026
0a9198d
Address VS Code extension review findings
jspahn80134 Jun 5, 2026
4a92941
Merge branch 'main' into main
jspahn80134 Jul 13, 2026
44364c9
Route capture through tokenized web service
jspahn80134 Jul 13, 2026
6d597ae
Fix VS Code capture lint checks
jspahn80134 Jul 14, 2026
0f07fa3
Fix overall test fixture lookup
jspahn80134 Jul 14, 2026
711aa0e
Allow empty test fixture directories
jspahn80134 Jul 14, 2026
89fd55e
Format test utility imports
jspahn80134 Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,14 @@
#
# dist build output
target/
/server/bindings/

# Runtime capture spools and generated capture exports.
/capture-spool/
/capture-metrics-*.csv
/capture-analysis-*/
/server/scripts/output
/server/scripts/capture-metrics-*.csv
/server/scripts/capture-analysis-*/

# CodeChat Editor lexer: python. See TODO.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,42 @@ Install the
[CodeChat Editor extension for Visual Studio code](extensions/VSCode/README.md).
For developers, see [building from source](docs/design.md).

Research capture
----------------

The VS Code extension can record dissertation study capture events when a
participant explicitly opts in. A participant first registers in the capture
portal, which emails a capture token. In VS Code, run **Manage CodeChat Editor
Capture** or **CodeChat Editor: Enter Capture Token** from the command palette,
paste the token, then turn on consent and recording from the same capture
manager.

The token is imported through the VS Code UI and persisted only in VS Code
SecretStorage. It is never written to workspace settings, repository files, or a
JSON configuration file. The extension asks CaptureWebService for token status;
the status item and capture manager show whether the token is accepted,
rejected, unavailable, or disabled by the portal. The participant ID used in
events comes from that status response, not from the token text.

CodeChat no longer connects directly to the remote capture database and no
longer reads or stores database credentials. The old local JSON database-secret
configuration path has been removed. Capture events now leave CodeChat only by
calling CaptureWebService with the portal-issued bearer token; any database
writer role remains inside the service deployment.

Events are sanitized, written to a durable local FIFO spool, then uploaded to
CaptureWebService. Spooled events carry only a non-secret token hash/service
identity so events from an old token are not uploaded under a new token. Offline
recording is allowed only after the same token and service URL have previously
been verified as capture-enabled; a token disabled by the portal remains
disabled while the service is unavailable. If the network or service is
unavailable after the token has been accepted at least once, queued events remain
in the spool and upload as soon as the matching token and service are available
again. The capture service endpoint can be changed in the user-level
`CodeChatEditor.Capture.ServiceBaseUrl` setting; workspace values are ignored
for this token-bearing endpoint. Token-bearing requests require HTTPS except for
localhost development endpoints.

Structure
---------

Expand Down
44 changes: 41 additions & 3 deletions client/src/CodeChatEditor-test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ import {
// From [SO](https://stackoverflow.com/a/39914235).
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

const RENDER_TIMEOUT_MS = 10000;
const MOCHA_TEST_TIMEOUT_MS = RENDER_TIMEOUT_MS + 5000;

const waitFor = async (
description: string,
predicate: () => boolean,
timeoutMs = RENDER_TIMEOUT_MS,
) => {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
if (predicate()) {
return;
}
await sleep(100);
}
assert.fail(`Timed out waiting for ${description}.`);
};

// Tests
// -----
//
Expand Down Expand Up @@ -97,25 +115,45 @@ window.CodeChatEditor_test = () => {
});
});

test("GraphViz, Mathjax, Mermaid", async function () {
// Wait for the renderers to run.
await sleep(1500);
test("GraphViz, Mathjax, Mermaid", async function (this: Mocha.Context) {
this.timeout(MOCHA_TEST_TIMEOUT_MS);

// Make sure GraphViz includes a `div` at the top of the shadow
// root, with a `svg` inside it.
const getGraphVizRoot = () =>
document.getElementsByTagName("graphviz-graph")[0]
?.shadowRoot?.children[0];
await waitFor(
"GraphViz SVG",
() => getGraphVizRoot()?.children[0]?.tagName === "svg",
);
const gv =
document.getElementsByTagName("graphviz-graph")[0]
.shadowRoot!.children[0];
assert.equal(gv.tagName, "DIV");
assert.equal(gv.children[0].tagName, "svg");

// Mermaid graphs start with a div.
const getMermaidRoot = () =>
document.getElementsByTagName("wc-mermaid")[0]?.shadowRoot
?.children[0];
await waitFor(
"Mermaid SVG",
() => getMermaidRoot()?.children[0]?.tagName === "svg",
);
const mer =
document.getElementsByTagName("wc-mermaid")[0].shadowRoot!
.children[0];
assert.equal(mer.tagName, "DIV");
assert.equal(mer.children[0].tagName, "svg");

// MathJax has its own stuff.
await waitFor(
"MathJax containers",
() =>
document.getElementsByTagName("mjx-container")
.length === 2,
);
assert.equal(
document.getElementsByTagName("mjx-container").length,
2,
Expand Down
3 changes: 3 additions & 0 deletions client/src/CodeChatEditor.mts
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,9 @@ export const on_error = (event: Event) => {
let err_str: string;
if (event instanceof ErrorEvent) {
err_str = `${event.filename}:${event.lineno}: ${event.message}`;
if (event.error?.stack) {
err_str += `\n${event.error.stack}`;
}
} else if (event instanceof PromiseRejectionEvent) {
const reason = event.reason;
let userMessage = "An unexpected error occurred. Please try again.";
Expand Down
4 changes: 4 additions & 0 deletions client/src/shared.mts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import { CodeMirrorDocBlockTuple } from "./rust-types/CodeMirrorDocBlockTuple.js
import { UpdateMessageContents } from "./rust-types/UpdateMessageContents.js";
import { ResultOkTypes } from "./rust-types/ResultOkTypes.js";
import { ResultErrTypes } from "./rust-types/ResultErrTypes.js";
import { CaptureEventWire } from "./rust-types/CaptureEventWire.js";
import { CaptureStatus } from "./rust-types/CaptureStatus.js";

// Manually define this, since `ts-rs` can't export `webserver.MessageResult`.
type MessageResult = { Ok: ResultOkTypes } | { Err: ResultErrTypes };
Expand All @@ -55,6 +57,8 @@ export type {
CodeMirror,
CodeMirrorDiffable,
CodeMirrorDocBlockTuple,
CaptureEventWire,
CaptureStatus,
CodeChatForWeb,
EditorMessage,
EditorMessageContents,
Expand Down
43 changes: 40 additions & 3 deletions docs/implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,46 @@ Inside the client:
The entire VSCode interface is contained in the extension, with the NAPI-RS glue
in the corresponding library.

Does this make more sense to place in the TOC? Or is it too wordy there? I think
a diagram as an overview might be helpful. Perhaps the server, client, etc.
should have its of readme files providing some of this.
### Capture path

Dissertation capture is a web-service-only path. The VS Code extension stores
the portal-issued capture token in VS Code SecretStorage after the user enters
it through the capture manager or **CodeChat Editor: Enter Capture Token**
command. The token is never written to settings, workspace files, or a JSON
configuration file. The extension keeps only non-secret participant/instance
metadata in VS Code global state and asks CaptureWebService for token status
before recording user events. The participant ID in each event comes from that
status response. The extension also caches the last capture-enabled decision for
the same token hash and service URL, so offline fallback cannot turn a
portal-disabled token back into a recordable state.

When consent, recording, and token status allow capture, the extension sends the
same capture event shape to the local CodeChat server through the existing
NAPI-RS bridge. The server hashes raw local file paths, removes any raw path
fields from event data before local persistence, writes every event to a durable
`capture-spool` FIFO directory, and uploads batches to
`POST /v1/capture/events` using
`Authorization: Bearer <token>`. CodeChat does not read database credentials
from disk and does not connect directly to PostgreSQL; the database schema under
`server/scripts/` documents the server-side table used by CaptureWebService.
The previous local JSON database-secret configuration path is intentionally
removed; any database writer role or password must remain server-side in the
CaptureWebService deployment.
The service endpoint is read only from the user/application-level
`CodeChatEditor.Capture.ServiceBaseUrl` setting; workspace values are ignored so
a repository cannot redirect a stored token. Bearer-token requests require
HTTPS, with `http://localhost` and `http://127.0.0.1` allowed only for local
development.

The upload worker deletes spooled events only after the service returns `202`.
After a token has been accepted at least once, transient service or network
failures still allow local recording and keep events queued for retry. Each
spool record includes a non-secret token hash and service URL identity; the
worker uploads only records matching the currently configured token/service, so
events queued under an old token remain local until that matching token is
configured again. Authentication failures pause upload until a valid token is
entered, malformed or oversized events are moved to the spool quarantine
directory, and blocking service calls use bounded request timeouts.

<a id="an-implementation"></a>Architecture
------------------------------------------
Expand Down
3 changes: 2 additions & 1 deletion extensions/VSCode/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
# NPM
node_modules/
out/
.test-output/

# Server support files
log4rs.yml
Expand All @@ -33,5 +34,5 @@ src/index.d.ts
src/index.js
src/codechat-editor-client.win32-x64-msvc.node
.windows/

*.log
# CodeChat Editor lexer: python. See TODO.
2 changes: 2 additions & 0 deletions extensions/VSCode/.vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ src/**

# Omit libraries, since `esbuild` takes care of packaging.
node_modules/
.test-output/**

# Omit VSCode config.
.vscode/**
Expand All @@ -41,6 +42,7 @@ target/**
# Misc files not needed in a package.
.eslintrc.yml
.gitignore
out/*.test.cjs
build.rs
Cargo.lock
Cargo.toml
Expand Down
Loading
Loading