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. If no workspace folder is open it stops there. Otherwise it builds the jj subprocess client for the first workspace folder, wraps it in ApiImpl, and builds JjUi with that API. JjUi registers the merge-conflict feature (tracker, decorations, CodeLens and two commands), a read-only file system for the jj-commits scheme, 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, which the client and the file system providers now use.

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, and logs the version. With no workspace folder it logs that and returns. Otherwise it creates a SubprocessClient for the first workspace folder, wraps it in ApiImpl, constructs JjUi with the context and the API, and pushes it too. JjUi's base constructor calls activateMergeConflict, which creates the conflict tracker, decorator, CodeLens provider and the two accept commands, then registers a CommitIdFileSystemProvider for the jj-commits scheme as case sensitive and read-only. 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; JjUi's dispose first clears the focus-mode config cache. 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. no workspace folder → logInfo("No workspace folders found"), return 4. api = new ApiImpl(new SubprocessClient(root)) 5. new JjUi(context, api) → context.subscriptions 6. logInfo("Extension activated successfully") JjUiBase constructor(api) activateMergeConflict(): tracker, decorator, CodeLens, two jj.mergeConflict.* commands registerFileSystemProvider("jj-commits", new CommitIdFileSystemProvider(api), {isCaseSensitive: true, isReadonly: true}) JjUi constructor (after super(api)) registerWebviewViewProvider( "jj-dojo.commit-graph", new CommitGraphViewProvider(extensionUri), {retainContextWhenHidden: true}) deactivate() → removeGlobalLogger() VS Code then disposes context.subscriptions: JjUi.dispose() clears the focus-mode config cache, then disposes its list in order (merge conflicts, jj-commits provider and registration, view registration); the logger closes its channel

1. package.json contributions

The manifest (source) is short: it declares one setting, one colour, two commands and one view. 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.configurationjj.graph.focusMode: boolean, default falseThe one setting: "In focus mode, the commit graph only shows commits on the current subgraph, and hides the rest of the commits." (source). At this pin only JjUiBase.dispose touches the module that reads it, to clear its cache; nothing reads the value yet (see gaps).
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 file-system watcher, which nothing creates yet), webview components and icons, the icon-theme JSON parser, the webview UI framework, and safe HTML (source).

2. activate and deactivate

activate does these 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. Reads the first workspace folder's path. If there is none, it logs No workspace folders found and returns, so nothing else is registered: no merge-conflict support and no graph view.
  4. Creates a SubprocessClient rooted at that folder and wraps it in ApiImpl, the implementation of the UI-facing Api interface. Its one method, readFileAtCommit, forwards to the client.
  5. Constructs JjUi(context, api) and pushes it onto context.subscriptions.
  6. 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" and keeps the Api it is given. Its constructor calls activateMergeConflict(), then registers the commit file system. JjUi adds the parts "only used externally": after super(api) 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).
2registerFileSystemProviderA CommitIdFileSystemProvider over the API for the jj-commits scheme, with isCaseSensitive: true and isReadonly: true. Both the provider and its registration go into disposables (source). It serves a file's content at a commit, for URIs of the form jj-commits://<path>?commitId=…&opId=…&showParents (source); the read path is on roadmap. Nothing in the extension opens such a URI yet.
3registerWebviewViewProviderFor 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() first calls removeFocusModeConfigCache(), then 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. When activation returned early for want of a workspace folder, only the logger is there to dispose.

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

extension.ts only uses logInfo. Of the wired code, the subprocess client and the file system providers throw through logAndShowInternalError: for a failed jj template parse, an unknown file status, a malformed jj-commits URI, or a write to the read-only file system (see roadmap). Nothing calls the two setGlobal… hooks, so no error callback or feedback provider is installed. The machinery is tested in logging_test.ts.