In short: VS Code activates the extension once startup has finished, or as soon as a workspace containing
.jj opens. activate creates an output-channel logger and installs it as the global
logger, then logs the version. It then builds JjUi, which registers the merge-conflict
feature (tracker, decorations, CodeLens and two commands) and then the commit graph webview view. Every
piece is a Disposable chained into context.subscriptions, so VS Code tears it
all down on deactivation. Errors go through one JjError type and a small set of
log… helpers. At this pin nothing outside the logging module calls the error helpers yet.
1. package.json contributions
The manifest (source) is short, because only two features are wired up at this pin. Field by field:
| Field | Value | Meaning |
|---|---|---|
name / displayName / publisher | jj-dojo / Jujutsu Dojo / google | Extension identity (source). |
version | 0.0.1 | (source) |
packageManager | pnpm@9.1.0 | The one place the pnpm version is set; Bazel and CI read it from here (source). |
engines.vscode | ^1.118.0 | Minimum VS Code version (source). |
extensionKind | ["workspace"] | Runs where the workspace is, for example on the remote side of a Remote-SSH session, where the repository and jj live (source). |
categories | ["SCM Providers"] | (source) |
activationEvents | onStartupFinished, workspaceContains:.jj | Activate after startup in any window, or right away when a folder containing .jj is opened (source). |
main | ./src/extension.js | The compiled entry point, built from src/extension.ts (source). |
contributes.configuration | {} | No settings yet (source). |
contributes.colors | jj.mergeConflict.thirdSideBackground: light #ff800033, dark #ff983133 | The only themable colour: the background of a conflict's third side (source). It is used by the merge-conflict decorations, which are registered in activateMergeConflict. |
contributes.commands | jj.mergeConflict.acceptAllSides ("Accept all sides"), jj.mergeConflict.acceptOneSide ("Accept one side") | Invoked from the conflict CodeLens (source). |
contributes.menus | {} | No menu entries (source). |
contributes.views.scm | jj-dojo.commit-graph, name "JJ Graph", type: webview, visibility: visible | The commit graph, as a webview view inside the Source Control container (source). |
scripts | build, package, test, lint/fix/clean (gts), addlicense, dep:add/dep:update/dep:install | Thin wrappers over Bazel and gts; the dep:* scripts run Bazel's pinned pnpm (source). |
dependencies | @parcel/watcher, @vscode-elements/elements, @vscode/codicons, jsonc-parser, lit, safevalues | Runtime libraries: native file watching (used only by the not-yet-wired file-system watcher), webview components and icons, the icon-theme JSON parser, the webview UI framework, and safe HTML (source). |
2. activate and deactivate
activate does four things, in order:
- Creates an
OutputChannelLogger, pushes it ontocontext.subscriptions, and installs it withsetGlobalLogger. - Logs
Extension version: ${packageJSON.build}.package.jsonhas nobuildfield at this pin, and thevsixtarget packages it unchanged, so outside tests this readsundefined. The upstream test suppliesbuild: 'test-version'(test). - Constructs
JjUiand pushes it ontocontext.subscriptions. - Logs
Extension activated successfully.
deactivate only removes the global logger. Everything
else is released by VS Code disposing context.subscriptions.
3. What ui.ts registers, and in which order
ui.ts splits the UI in two (source).
JjUiBase is "shared between external and internal". Its constructor calls
activateMergeConflict(). JjUi adds the parts "only used externally": after
super() it registers the webview view provider. So merge-conflict support is always
registered first.
| Order | Registration | Details |
|---|---|---|
| 1 | activateMergeConflict() | Creates a ConflictTracker, a ConflictDecorator and a ConflictCodeLensProvider on top of it, and registers the two jj.mergeConflict.* commands. It returns them as one vscode.Disposable.from(…) (source). |
| 2 | registerWebviewViewProvider | For CommitGraphViewProvider.viewType = jj-dojo.commit-graph (source), with webviewOptions.retainContextWhenHidden: true, so the graph keeps its state while the SCM view is hidden (source). How the view talks to its webview is on graph-protocol. |
4. Disposables and lifecycle
Every resource ends up in one tree of Disposables rooted at
context.subscriptions: the logger, and JjUi. JjUiBase keeps a
disposables array, and its dispose() calls the shared helper
dispose. That helper disposes each entry in array
order, then empties the array, so a second dispose() is a no-op. The merge-conflict bundle
disposes its tracker, decorator, CodeLens provider and command registrations together.
5. Logging
| Piece | What it is | Source |
|---|---|---|
Logger | An interface with info(message) and error(message | Error, ...args). | logger.ts |
OutputChannelLogger | The only implementation. It is a log output channel named JJ Extension, created with {log: true}, which forwards to channel.info / channel.error. | output_channel_logger.ts |
| Global logger | A module-level variable. setGlobalLogger / removeGlobalLogger install and remove it, and every log… call is a no-op while none is installed. | logging.ts |
logInfo / logAndShowInfo | Log at info level. The second also shows an information notification. | logging.ts |
6. Error reporting
JjError is the project's single error type. It carries
four optional flags: isShownToUser, isLogged, isNetworkError and
isInternalError. getMessage() prefixes Internal Error - for
internal errors. addPrefix(p) rewrites the message to p: message.
JjError.from(x) normalises anything thrown: a JjError as is, a string, an
Error (message only), or JSON.stringify of anything else. A private
prefixes array is declared but never used, since addPrefix edits the message
directly. isNetworkError isn't set anywhere at this pin.
| Helper | Behaviour | Source |
|---|---|---|
logError(e) | Normalises to JjError. If it isn't logged yet, marks it and calls logger.error(error, extra). extra is the global error callback's result, passed as a separate argument rather than appended to the message (the test expects ['ERROR', 'some error', 'extra info']). | logging.ts, test |
logAndShowUserError(e) | logError, then, once per error, showErrorMessage(getMessage()). | logging.ts |
logAndShowInternalError(e) | Marks the error internal, logs it, and once per error hands it to the global feedback provider. It marks isShownToUser whether or not a provider is registered, and doesn't call showErrorMessage itself. | logging.ts |
| Error callback | setGlobalErrorCallback(() => string). Its result is attached to every logged error. | error_callback.ts |
| Feedback provider | setGlobalFeedbackProvider((error) => void). It is invoked for internal errors and may, per its comment, "pop up a dialog to the user asking to file a bug". | feedback.ts |
At this pin no code outside src/logging/ calls logError, the
logAndShow… helpers, or the two setGlobal… hooks. extension.ts
only uses logInfo. The machinery exists, and is tested in
logging_test.ts, ahead of the features that will report errors.