Skip to content

feat(local): give Studio's job refusals codes the interface can translate - #2241

Merged
MODSetter merged 2 commits into
MODSetter:devfrom
ybai08:feat/studio-job-refusal-codes
Oct 13, 2026
Merged

MODSetter merged 2 commits into
MODSetter:devfrom
ybai08:feat/studio-job-refusal-codes

Conversation

@ybai08

@ybai08 ybai08 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What

A Studio job that is refused for a reason other than a missing model sent a bare sentence, so the panel showed it in English in every language under its translated alert title. It is the seventh producer on #1967, and the Studio half of the gap line in localization.md.

Five refusals in modules/artifacts/service.py now send the {"message", "code"} detail a missing model already sends:

Refusal From Code
"already generating" regenerate already_generating
"nothing is running" cancel nothing_running
"pick at least one source" create, with no ids or with a scope that holds nothing no_sources
"the chosen sources are still indexing" create, with a scope whose sources are all still indexing sources_indexing
"None of this artifact's sources are left." regenerate sources_gone
  • studio-error-text.ts has a line for each, studio_error_<code>, so messageFrom shows it for a create, a regenerate and a cancel. A code it has no line for still shows the backend's sentence.
  • The five lines in the 9 other languages.
  • localization.md names the codes where it describes Studio's, and the gap line now lists what is left.

The backend's sentences and status codes are unchanged. tests/integration/artifacts/test_regenerate_scope.py read one of them as the whole detail; it reads detail["message"] now.

Left for later producers, as in the plan on the issue: the two refusals of the shared load_selected_sources (a source in another workspace, and "sources are still indexing: [ids]", which the chat and agent threads raise too), a format's own option errors, the refine refusals (studio_documents/service.py has its own bare "already generating"), and the revised copies.

Why

#1967: backend prose reaches the screen untranslated. A code lets each language say it.

Refs #1967

How to test

Run on macOS arm64. The branch is on dev at f887af104; each line says which base it was run on.

Backend, from surfsense_local/backend:

  • tests/integration/artifacts/test_refusal_codes.py (new, 6 tests): before the change all 6 fail, each with the bare sentence where the code is expected. After it they pass.
  • uv run pytest tests/integration/artifacts, after the rebase: 171 passed.
  • uv run pytest tests/integration, before the rebase (on f2a021f3f): 1330 passed, 122 skipped.
  • uv run pytest tests/unit, before the rebase: 4020 passed, 7 skipped, 1 failed: tests/unit/llm/catalog/local/engines/llamacpp/models_folder/test_preset.py::test_repricing_unchanged_models_does_not_rewrite_the_preset. It compares a file's modification time and is flaky on this Mac without this change too: run alone three times with my changes stashed it failed twice and passed once, and with them it passed once and failed twice. It does not touch this code; test(local): hold host memory still where repricing must leave the preset alone #2242 fixes the test (its cache-ram follows the host's free memory).
  • uv run ruff check .: clean. ruff format --check on the three files I touched: clean.

Frontend, from surfsense_local/frontend:

  • studio-error-text.test.ts (new, 7 tests): before the lines were added, the 5 that name a code fail with null.
  • pnpm test, before the rebase: 836 passed in 105 files. After it I reran pnpm test src/features/studio: 166 passed in 16 files.
  • pnpm typecheck, pnpm lint: clean. prettier --check on the three files: clean.
  • pnpm translations:verify and node scripts/check_translations.mjs, after the rebase: 1315 keys in 10 languages, 0 problems; pnpm translations leaves en.json unchanged.
  • python scripts/check_docs.py, after the rebase: 154 files, 0 problems.

Mutations, one at a time, each with the tests rerun, before the rebase: 18 applied, 18 caught. In the backend: each refusal sent bare again or under another refusal's code, an all-indexing scope reported as no sources, and the helper dropping the code, sending the sentence bare, or putting the code under another key. In the frontend: each of the five lines removed, and two codes' lines swapped.

Not run: the app itself. I did not trigger these refusals in a running build, in English or in another language; the translations are checked by the two catalog checks and by reading, not on screen. Nobody who reads those nine languages has reviewed them.

Summary by CodeRabbit

  • Bug Fixes
    • Studio now provides clearer, localized messages when generation is already underway, when there’s nothing running to cancel, or when sources are missing, still indexing, or no longer available.
    • Error messages are available in English, German, Spanish, French, Hindi, Japanese, Korean, Brazilian Portuguese, Russian, and Chinese. Unknown refusal messages continue to use the text provided by the service.

…late

A create, regenerate or cancel that Studio refuses for a reason other than
a missing model sent a bare sentence, so it was English in every language.
Five of them now carry a code beside the sentence: already_generating,
nothing_running, no_sources, sources_indexing and sources_gone.
studio-error-text.ts has a line for each, in all ten catalogs; a code it
has no line for still shows the backend's sentence.

Refs MODSetter#1967
@vercel

vercel Bot commented Oct 12, 2026

Copy link
Copy Markdown

@ybai08 is attempting to deploy a commit to the Rohan Verma's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: MODSetter/SurfSense/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6d4a1ada-94df-43d3-a65f-9f095d86eb43
📥 Commits

Reviewing files that changed from the base of the PR and between 02e291a and 441363d.

📒 Files selected for processing: 15
  • docs/architecture/localization.md
  • docs/architecture/studio.md
  • surfsense_local/frontend/src/features/studio/studio-error-text.test.ts
  • surfsense_local/frontend/src/features/studio/studio-error-text.ts
  • surfsense_local/frontend/src/features/studio/studio-panel.test.tsx
  • surfsense_local/frontend/translations/de.json
  • surfsense_local/frontend/translations/en.json
  • surfsense_local/frontend/translations/es.json
  • surfsense_local/frontend/translations/fr.json
  • surfsense_local/frontend/translations/hi.json
  • surfsense_local/frontend/translations/ja.json
  • surfsense_local/frontend/translations/ko.json
  • surfsense_local/frontend/translations/pt-BR.json
  • surfsense_local/frontend/translations/ru.json
  • surfsense_local/frontend/translations/zh-CN.json
🚧 Files skipped from review as they are similar to previous changes: 12
  • surfsense_local/frontend/translations/ru.json
  • surfsense_local/frontend/translations/es.json
  • surfsense_local/frontend/src/features/studio/studio-error-text.ts
  • surfsense_local/frontend/src/features/studio/studio-error-text.test.ts
  • surfsense_local/frontend/translations/pt-BR.json
  • surfsense_local/frontend/translations/ko.json
  • surfsense_local/frontend/translations/de.json
  • surfsense_local/frontend/translations/ja.json
  • surfsense_local/frontend/translations/fr.json
  • surfsense_local/frontend/translations/hi.json
  • surfsense_local/frontend/translations/en.json
  • surfsense_local/frontend/translations/zh-CN.json

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

The backend now returns structured codes for selected Studio artifact refusals. The frontend maps those codes to localized messages. Tests verify the response codes and displayed text.

Changes: Studio refusal handling

Layer / File(s) Summary
Structured artifact refusal responses
surfsense_local/backend/modules/artifacts/service.py, surfsense_local/backend/tests/integration/artifacts/*, docs/architecture/studio.md, docs/architecture/localization.md
Selected refusals retain their HTTP status and message while adding a code. Integration tests check these responses. The architecture documentation lists the codes and describes remaining localization gaps.
Localized frontend refusal messages
surfsense_local/frontend/src/features/studio/*, surfsense_local/frontend/translations/*.json
The frontend maps five refusal codes to localized messages. Tests cover the mappings, unmapped errors, and the message shown for a sources_gone refusal.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Merge Risk: ⚪ Minimal · up to 44136

Studio refusal messages retain their existing behavior while gaining localized text for the five coded cases. No actionable merge-blocking risk is identified.

### Architecture Summary

Architecture risk: 🔵 Low · up to 44136

The change affects 2 systems.

Changed systems: surfsense_local, docs

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — surfsense_local (ui) was modified; 16 changed files map to changed impact.
  • observed — docs (service) was modified; 2 changed files map to changed impact.

Before / after behavior

  • observed — Modified behavior in surfsense_local/backend/modules/artifacts/service.py: Regeneration of a pending or processing artifact still returns HTTP 409 with “already generating,” but the refusal now also includes the code already_generating.
  • observed — Modified behavior in surfsense_local/backend/modules/artifacts/service.py: Cancellation still returns HTTP 409 with “nothing is running” when no job can be cancelled; the error now also includes the code nothing_running.
  • observed — Modified behavior in surfsense_local/backend/modules/artifacts/service.py: Adds helpers for structured refusals containing a message and code, and for the HTTP 422 no-sources refusal, coded no_sources.
  • observed — Modified behavior in surfsense_local/backend/modules/artifacts/service.py: Source resolution uses the no_sources refusal for empty explicit selections and scopes with no resolved IDs and no indexing sources. When a scope has no resolved IDs because sources are indexing, it returns HTTP 409 with the existing message and code sources_indexing.
Pre-merge checks: Passed 5
Passed Passed checks: 5 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: adding translatable codes to Studio job refusals.
Docstring Coverage Passed Docstring coverage is 81.25% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 16 functions across 6 files. (12 skipped: 1…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MODSetter

Copy link
Copy Markdown
Owner

Reviewed this. The code is right:

  • all five refusals carry {"message", "code"} from _refused() (service.py:145-151), through responseError() and translatedStudioError(), to the panel alert;
  • the backend's sentences and status codes are unchanged;
  • an unknown code falls back to the backend's sentence;
  • swapping any two codes fails a test.

Two things before it can merge.

1. The docs don't follow the change

  • docs/architecture/studio.md still describes only the needs_* code, at :15, :253, :257, :261 and :299. AGENTS.md:47 asks for the doc in the same change, as feat(local): give a Studio format's unavailable reason a code to translate #2146 did on these same lines.
  • The Known gaps line (localization.md:90) is now a list, and it leaves out refusals still sent in English: the quiz and flashcard state refusals (artifacts/router.py:251 and :321-323, quiz_progress.py:150-224, flashcard_progress.py:105-146) and the artifact and file errors, which the viewers show raw. The old wording, "Studio's refusals other than a missing model", covered them, so keep it general until the last of them has a code.
  • That line also says "which the panel shows under its translated alert title", which isn't true for a Refine's own refusals (refine-box.tsx:235-241) or for Accept/Reject all (revised-copy-bar.tsx:120-124).

2. "None of its sources are left." has nothing for "its" to point at

en.json:1178 and studio-error-text.ts:31-35 shorten the backend's "None of this artifact's sources are left." to "its". Under the alert title "Studio action failed", "its" refers to nothing, and the comment at :4-9 says these lines mirror _refused(). In pt-BR the line became "Não resta nenhuma das suas fontes.", which reads as "none of your sources are left": "suas fontes" means "your sources" 13 times in that catalog. Keep "this artifact's" in English, and translate it that way, e.g. "…das fontes deste artefato".

Not blocking: localization.md:59 says the codes arrive "in its 409", but no_sources is a 422.

Push the fixes to this branch and it will be reviewed again.

…s in the Studio doc

Review of MODSetter#2241.

The interface's line for sources_gone had shortened the backend's "None of
this artifact's sources are left." to "its", which points at nothing under
the alert title, and in pt-BR read as "your sources". English keeps "this
artifact's" and the nine other catalogs say it the same way.

docs/architecture/studio.md names the codes where it described the refusals
(the job's steps, the three routes and the panel). localization.md's Known
gaps line is general again: it listed the uncoded refusals and left out the
quiz, flashcard, artifact and file ones, and said the panel's alert title
covers a Refine's and Accept all / Reject all's refusals, which show their
own. Its Backend text paragraph says no_sources is a 422.
@ybai08

ybai08 commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

Thank you. 441363d9a has both, and the non-blocking one.

1. The docs

  • docs/architecture/studio.md names the codes where it described the refusals: step 2 of the job (no_sources, and sources_indexing for a scope with nothing ready yet), the POST /studio/jobs row, the regenerate row (already_generating, and sources_gone beside its sentence), the cancel row (nothing_running), and the panel paragraph, which now says the alert shows the interface's sentence for those five codes and the API's for one it does not know. Step 2 also says that a source named by id in another workspace or still indexing has no code yet.
  • The Known gaps line in localization.md is general again: "Studio's refusals other than a missing model and a job that cannot start, be run again or be stopped", with the uncoded ones as examples, now including the quiz and flashcard state refusals and the artifact and file errors the viewers show as sent.
  • It no longer says the panel's alert title covers everything: the panel shows the ones it receives under its title, a Refine shows its own under its box, and Accept all and Reject all theirs under the bar, with no title.
  • Not blocking, but fixed: the Backend text paragraph says a job's refusal comes in a 409 or, for no_sources, a 422.

2. "None of this artifact's sources are left."

English keeps "this artifact's" (with the catalog's typographic apostrophe, like its other strings), and the nine other catalogs say it the same way, each with the word that catalog already uses for an artifact:

de Keine der Quellen dieses Artefakts ist mehr vorhanden.
es No queda ninguna de las fuentes de este artefacto.
fr Il ne reste aucune des sources de cet artefact.
hi इस आर्टिफ़ैक्ट का कोई भी स्रोत नहीं बचा है।
ja この生成物のソースは1つも残っていません。
ko 이 생성물의 소스가 하나도 남아 있지 않습니다.
pt-BR Não resta nenhuma das fontes deste artefato.
ru Ни одного из источников этого артефакта не осталось.
zh-CN 这个产物的来源已全部不存在。

I do not read all nine; they were written from the English against each catalog's existing lines for "this artifact" (studio_artifact_failed_toast_body), so a native reader may want to look at ja, ko, hi and zh-CN.

One thing in the panel test: the interface's English is now the backend's sentence word for word, so the assertion that the alert shows the interface's line and not the backend's tells them apart only by the apostrophe. There is a comment saying so.

How to test

From surfsense_local/frontend, all on 441363d9a:

  • pnpm translations (no change to commit afterwards), pnpm translations:verify, node ../../scripts/check_translations.mjs: 1315 keys in 10 languages, 0 problems.
  • pnpm test: 105 files, 838 tests passed.
  • pnpm typecheck and pnpm lint: exit 0. prettier --check on the three source files and the ten catalogs: clean.
  • python scripts/check_docs.py: 154 doc files, 0 problems.

Not run: the backend suites (no backend file changed in this commit) and the app itself.

@MODSetter
MODSetter merged commit 28ce714 into MODSetter:dev Oct 13, 2026
28 of 29 checks passed
@MODSetter

Copy link
Copy Markdown
Owner

Merged into dev, thanks. Both points are fixed: "this artifact's" now reads that way in every catalog, and the Known gaps line is general again. One wording thing for your next #1967 PR. "A job that cannot start, be run again or be stopped" (localization.md:59 and :90, studio.md:299) reads as the whole class, but only the five refusals you coded have a code. Some still send bare English:

  • sources.py:23-33: another workspace's source, or sources still indexing;
  • service.py:215-223: a bad podcast brief;
  • fits.py:34-35: a refine that no longer fits;
  • rerun.py:15-16: a revised copy's "already generating", which also makes studio.md:257 overstate.

Naming the five ("the five job refusals with a code") fixes all three places.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants