jj-dojo / Activation and wiring

Activation and wiring

What Jujutsu Dojo declares to VS Code in package.json, what runs when the extension activates, the order its features are registered in, and how logging and errors are set up.

On this page
  1. 1. package.json contributions
  2. 2. activate and deactivate
  3. 3. What ui.ts registers, and in which order
  4. 4. Disposables and lifecycle
  5. 5. Logging
  6. 6. Error reporting

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.

Jujutsu Dojo activation sequence VS Code reads package.json and activates the extension on onStartupFinished or workspaceContains:.jj. activate in extension.ts creates an OutputChannelLogger named "JJ Extension", pushes it to context.subscriptions and sets it as the global logger, logs the version, then constructs JjUi and pushes it too. JjUi's base constructor calls activateMergeConflict, which creates the conflict tracker, decorator, CodeLens provider and the two accept commands. JjUi's own constructor then registers the commit graph webview view provider for jj-dojo.commit-graph with retainContextWhenHidden. deactivate removes the global logger, and VS Code disposes everything in context.subscriptions. VS Code: activationEvents = onStartupFinished, workspaceContains:.jj main = ./src/extension.js; extensionKind = workspace activate(context) — src/extension.ts 1. new OutputChannelLogger() → context.subscriptions; setGlobalLogger(logger) 2. logInfo("Extension version: …") 3. new JjUi(context) → context.subscriptions 4. logInfo("… activated successfully") JjUiBase constructor activateMergeConflict(): ConflictTracker, ConflictDecorator, ConflictCodeLensProvider, jj.mergeConflict.acceptAllSides / acceptOneSide JjUi constructor (after super()) registerWebviewViewProvider( "jj-dojo.commit-graph", new CommitGraphViewProvider(extensionUri), {retainContextWhenHidden: true}) deactivate() → removeGlobalLogger() VS Code then disposes context.subscriptions: JjUi.dispose() disposes its list in order (merge-conflict bundle, then the view registration); the logger disposes its output channel

1. package.json contributions

The manifest (source) is short, because only two features are wired up at this pin. Field by field:

FieldValueMeaning
name / displayName / publisherjj-dojo / Jujutsu Dojo / googleExtension identity (source).
version0.0.1(source)
packageManagerpnpm@9.1.0The one place the pnpm version is set; Bazel and CI read it from here (source).
engines.vscode^1.118.0Minimum 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)
activationEventsonStartupFinished, workspaceContains:.jjActivate after startup in any window, or right away when a folder containing .jj is opened (source).
main./src/extension.jsThe compiled entry point, built from src/extension.ts (source).
contributes.configuration{}No settings yet (source).
contributes.colorsjj.mergeConflict.thirdSideBackground: light #ff800033, dark #ff983133The 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.commandsjj.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.scmjj-dojo.commit-graph, name "JJ Graph", type: webview, visibility: visibleThe commit graph, as a webview view inside the Source Control container (source).
scriptsbuild, package, test, lint/fix/clean (gts), addlicense, dep:add/dep:update/dep:installThin 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, safevaluesRuntime 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:

  1. Creates an OutputChannelLogger, pushes it onto context.subscriptions, and installs it with setGlobalLogger.
  2. Logs Extension version: ${packageJSON.build}. package.json has no build field at this pin, and the vsix target packages it unchanged, so outside tests this reads undefined. The upstream test supplies build: 'test-version' (test).
  3. Constructs JjUi and pushes it onto context.subscriptions.
  4. 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.

OrderRegistrationDetails
1activateMergeConflict()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).
2registerWebviewViewProviderFor 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

PieceWhat it isSource
LoggerAn interface with info(message) and error(message | Error, ...args).logger.ts
OutputChannelLoggerThe 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 loggerA module-level variable. setGlobalLogger / removeGlobalLogger install and remove it, and every log… call is a no-op while none is installed.logging.ts
logInfo / logAndShowInfoLog 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.

HelperBehaviourSource
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 callbacksetGlobalErrorCallback(() => string). Its result is attached to every logged error.error_callback.ts
Feedback providersetGlobalFeedbackProvider((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.