feat(notify,theia): a notification can land in the IDE, not only in a chat - #83
Merged
AndrejK666 merged 1 commit intoSep 9, 2026
Conversation
… chat Studio knows things the person working does not — an import finished, a publish was rejected, a schedule fired — and until now the only way to tell them was a chat message they may not be reading. `POST /studio-notify/v1/messages` takes a `workspace_id` instead of a `connection_id` and the message is shown in the Theia IDE of whoever has that workspace open. Same queue, same run, same retries: the editor is a second kind of *destination*, not a second notification system. `title` becomes the headline the IDE leads with, `text` the line beneath it, `link` an *Open* action (followed through Theia's opener service, http(s) only), and `level` is `info` | `warn` | `error` — an invented one is refused rather than shown as grey information. **`notifyEditor` existed only as a design.** The bridge contract (ADR-0010 §4) has described it since the bridge was written; this implements it on both sides: the protocol shapes and the `StudioRuntimeClient.onNotifyEditor` callback, a frontend controller that shows it through Theia's `MessageService`, the node endpoint that broadcasts to the browser clients, the `/notifyEditor` control route, and `TheiaControlClientV1::notify_editor` on the Rust side. It is the same shape as `openInEditor` and for the same reason: the node backend has no UI of its own, so the message goes to the clients that have one. **Two honest limits, both visible in the API.** A toast is only worth sending to somebody who is there, so the accept path resolves the workspace to a *live* session and refuses when there is none — with a sentence naming the workspace and suggesting a chat connection instead. And reaching a session is not the same as being seen: a session with no browser tab attached answers `shown: false`, and the run succeeds with a summary saying exactly that, because it is neither a failure to retry nor a delivery to celebrate. The IDE bridge is behind the opt-in `theia-bridge` feature, so the two places that talk to it are gated — but the wire shape is not: a build without the bridge still takes `workspace_id` and answers with the reason it cannot serve it, rather than having a route whose schema changes with a build flag. Verified. Backend: clippy `-D warnings` clean and the suite green in both feature builds (275 tests default, 277 with `theia-bridge`). On a running stack (config/postgres.yaml, `--no-default-features --features theia-bridge`) all four refusals are real: naming neither destination and naming both are 400s with the same sentence; `level: "critical"` is refused; and a workspace with no session answers "no IDE session to notify for workspace … Start a session, or send this to a chat connection instead" — with the bridge off, the inner reason names `theia_control_enabled`, and with studio-session live it names the missing session. Theia extension: `tsc --noEmit` clean for every file this touches, and `studio-backend-module.test.ts` passes with two new cases covering `shown: true` for a client that can display a notification and `shown: false` for a session whose clients cannot. Two things I could not verify here, said plainly. The toast itself needs a Theia image built from this source and a browser in front of it; the image build pulls the whole Theia plugin set and wants a registry secret, so CI's theia job is the first place that runs this code for real. And in the whole-extension jest run, four suites fail in my harness (the published image's `node_modules` against the repo's current jest config) — identically with and without these changes, and none of them a file this commit touches. Signed-off-by: Andrej Kuchma <Andrej.Kuchma@constructor.tech>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Comment |
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.
Studio knows things the person working does not — an import finished, a publish
was rejected, a schedule fired — and until now the only way to tell them was a
chat message they may not be reading. This puts it where they already are.
POST /studio-notify/v1/messages { "workspace_id": "…", # instead of connection_id "level": "warn", # info (default) | warn | error "title": "The repository import failed", "text": "GitHub answered 401 — the token was rotated.", "link": "http://localhost:8080/projects/14/sources" }Same queue, same run, same retries: the editor is a second kind of
destination, not a second notification system.
titleis the headline theIDE leads with,
textthe line beneath it,linkan Open action (followedthrough Theia's opener service, http(s) only).
notifyEditorexisted only as a designThe bridge contract has described it since it was written
(
docs/theia-bridge-contract-v1.md§4, ADR-0010). This implements it on bothsides:
StudioRuntimeClient.onNotifyEditor, afrontend controller that shows the message through Theia's
MessageService,the node endpoint that broadcasts to the browser clients, and the
/internal/theia/v1/notifyEditorcontrol route. Same shape asopenInEditorand for the same reason: the node backend has no UI of its own, so the message
goes to the clients that have one.
TheiaControlClientV1::notify_editorplus theNotifyEditormodels, and a second destination instudio-notify.Two honest limits, both visible in the API
A toast is only worth sending to somebody who is there. The accept path
resolves the workspace to a live session and refuses when there is none, with
a sentence naming the workspace and suggesting a chat connection instead. A chat
message waits in a channel; an IDE notification has nobody to wait for.
Reaching a session is not the same as being seen. A session can run with no
browser tab attached. The bridge answers
shown: falsefor that, and the runsucceeds with a summary saying exactly that — neither a failure to retry nor a
delivery to celebrate. A caller that needs certainty a person saw something
should not be using a toast for it.
Everything the bridge itself fails with is retried (a session restarting, a
control port not yet listening); there is no permanent case, so the attempt cap
ends it and the dead letter carries the last reason.
The feature gate
The IDE bridge is behind the opt-in
theia-bridgefeature, so the two placesthat talk to it are gated — but the wire shape is not. A build without the
bridge still takes
workspace_idand answers with the reason it cannot serveit, rather than having a route whose schema changes with a build flag.
Verification
Backend: clippy
-D warningsclean and the suite green in both featurebuilds — 275 tests default, 277 with
theia-bridge.On a running stack (
config/postgres.yaml,--no-default-features --features theia-bridge), all four refusals are real:naming neither destination and naming both are 400s with the same sentence;
level: "critical"is refused withexpected info | warn | error; and aworkspace with no session answers "no IDE session to notify for workspace … .
Start a session, or send this to a chat connection instead" — where the inner
reason names
theia_control_enabledwhen the bridge is off, and the missingsession when studio-session is live.
Theia extension:
tsc --noEmitclean for every file this touches, andstudio-backend-module.test.tspasses with two new cases —shown: truefor aclient that can display a notification,
shown: falsefor a session whoseclients cannot.
Not verified here, and worth knowing. The toast itself needs a Theia image
built from this source with a browser in front of it; that image build pulls the
whole Theia plugin set and wants a registry secret, so CI's theia job is the
first place this code runs for real. And in the whole-extension jest run four
suites fail in my local harness (the published image's
node_modulesagainstthe repo's current jest config) — identically with and without these changes,
and none of them a file this PR touches.