Manage your Databricks jobs from your phone. π
A mobile-first Google Apps Script web app for Databricks β
dashboard, run history, AI-powered diagnostics, and notebook fixes,
without ever opening Databricks.
No server. No infra. Just a Web App URL you open on your phone.
πΈ Full screenshot gallery β docs/SCREENSHOTS.md β dashboard, AI diagnosis & fix, connection setup, and the optional AI hint.
π Setup & user guide β English Β· EspaΓ±ol β how to access, install on iOS/Android, and a full feature tour. Share it with your team.
- Job dashboard with status dots (last 5 runs per job)
- Global run history (Runs tab)
- Job detail: expandable task list (SUCCESS) or AI diagnosis + Repair (FAILED)
- Trigger any job (βΆ Run Now) and repair failed tasks without opening Databricks
- Think Fix β an LLM inspects real table schemas from Hive Metastore via the Clusters Execute Command API and proposes a concrete find β replace patch for your notebook. Optional hint field: before diagnosing you can type a free-text suggestion (e.g. "the table was renamed to X") that the LLM weighs heavily β leave it empty for a fully automatic diagnosis
- Multi-task failures, sub-jobs, and chained repairs (
latest_repair_idauto-detected)
- Pinned job widget β fixed card above the job list: status badge + progress bar + elapsed time. Tap β detail view.
- Auto-refresh 30s β activates when a job is RUNNING. Countdown in the header. Stops automatically.
- Quick filters β
AllΒ·FailedΒ·Runningchips. Client-side, combined with text search. - Favorites β
β toggle on any job row. Favorites float to the top. Persisted cross-device via
UserProperties. - Cancel run β
β Cancelbutton when a job is RUNNING. Confirm dialog + auto-refresh. - Run history β last 10 runs per job in the detail view (date, duration, status).
- Fixes tab β log of all applied patches: notebook path, timestamp, find β replace preview. Max 50, FIFO.
- Dark/light mode β SVG toggle, persisted in
localStorage.
- Per-user config β host, token, LLM endpoint, and pinned job stored per-user in
UserProperties. Credentials never shared viaScriptProperties. - Setup wizard β first-run full-screen modal. App blocks until host + token are configured.
- Pin Job (π) β pin any job as the featured widget. Toggle from the job row or the Settings tab.
- Settings tab (β) β edit connection, choose LLM endpoint from a live dropdown, manage pinned job.
- Configurable LLM endpoint β populated dynamically from
/api/2.0/serving-endpoints(READY only). - Marquee for long names β job names that overflow scroll horizontally.
- Language toggle EN/ES β full bilingual UI in Settings tab, persisted per-user in
UserProperties. LLM prompts inpensarFixalso switch language. - Diff visual before applying fix β unified diff (LCS line-by-line) replaces the two separate
<pre>blocks. Lines removed in red (β), lines added in green (+). - Tab Clusters β live list of all workspace clusters with state badges (Running/Terminated/Starting/Error). Start and Terminate buttons without leaving the app.
- Scheduler one-shot β schedule any job to run at a specific date/time. Uses GAS time-based triggers (
ScriptApp.newTrigger). Pending scheduled runs visible and cancellable from Settings. - Performance chart β SVG bar chart of the last 10 run durations (colored by state) with fail-rate badge, shown in every job detail view.
- Task DAG β visual dependency graph rendered as SVG in the job detail view. Nodes colored by state (green/red/blue), cubic BΓ©zier edges, horizontal column layout by depth.
- Drill into failed sub-jobs β when a failed task is a
run_job(sub-job), the detail view shows an "open child job β" button that navigates straight to the child run (where the real notebook failure + Think Fix live). Backend resolvessubjob_id+subjob_name. - Repair continues the DAG β
repairRunnow sends a flatrerun_tasksarray (fixed a[["task"]]nesting bug β 400) plusrerun_dependent_tasks: true, so repairing a failed task also re-runs the downstream tasks it had blocked.latest_repair_idpersisted per-run inUserProperties(the API never returnsrepair_history). - Parameter editor β β button in the detail header opens a modal of editable key-value rows (pre-filled from the job's default
base_parameters). Triggers the job withnotebook_paramsoverride viatriggerJob(jobId, params). - Global search β magnifier in the header opens a full-screen overlay that searches job names, job IDs, run IDs and run job names across loaded data. Tap a result to jump to its detail.
- Run comparison β collapsible picker of two runs of the same job in the detail view. Side-by-side table of per-task duration with a signed delta in seconds (green faster / red slower). Backend adds per-task
duration_mstorun_history_with_tasks.
- Multiple workspaces β save several Databricks workspaces (name + host + token) and switch the active one from Settings β Workspaces. The whole app (jobs, runs, clusters, fixes) reloads for the active workspace. Each workspace keeps its own token, LLM endpoint and pinned job; language is global. Config migrates automatically from the old single-workspace shape. All per-user in
UserPropertiesβ no shared credentials, no backend. - Universal icon UI + spinners β loading states render an animated spinner; primary actions are compact icons (
βΆrun,βapply,βcancel,βΉback,π§ Repair,π‘ Think Fix) with localized tooltips (data-i18n-title). Fully bilingual EN/ES including the AI diagnosis and the Task Code Viewer (previously hard-coded). Expandable Fixes entries with full red/green diffs.
- Paginated run detail β
runs/getreturns at most 100 tasks per page. Every run read (getJobDetail,_fetchRunsWithTasks,getOrchestratorStatus) now followsnext_page_token, so jobs with hundreds of tasks show every failed task, the full DAG and a real progress bar. - Director jobs (tasks that are themselves jobs) β a job whose tasks are all
run_job_taskis handled as a first-class case. The sentinel is detected structurally (the DAG leaf runningrun_if=ALL_DONE) instead of by name, so a rename cannot hide it. Drilling into a failed sub-job opens the run that parent actually triggered, not the child job's latest run. Repair warns before it re-runs a whole child job. The progress bar labels sub-jobs as sub-jobs, and the parameter editor reads job-levelparameters, not only taskbase_parameters. - Sentinel-aware diagnosis β a sentinel task (one that exists only to force the run to
FAILEDwhen something inside it failed, becauserun_if=ALL_DONEwould otherwise close the run green) is detected, pushed to the bottom of the failed-task list and rendered with asentinel β not the causechip: no AI diagnosis, no Think Fix, excluded from Repair. The real failure becomes the primary card. If the sentinel is the only failure, it is treated as a normal failure and diagnosed.
Phone
β
βΌ
[Web App URL]
β google.script.run.*()
βΌ
[codigo.gs β Apps Script V8]
β
ββ getDashboard()
β ββ /api/2.1/jobs/list + /api/2.1/jobs/runs/list (last 5 runs per job)
β
ββ getOrchestratorStatus()
β ββ /api/2.1/jobs/runs/list?job_id={pinned_job_id}&limit=1
β ββ (if RUNNING) /api/2.1/jobs/runs/get β count SUCCESS tasks
β
ββ getJobDetail(jobId)
β ββ /api/2.1/jobs/runs/list?limit=10 β run_history
β ββ _getRunDetailPaged() β /api/2.2/jobs/runs/get (follows next_page_token, 100 tasks/page)
β ββ (if FAILED) tasks + thinkFix (sentinel tasks skipped)
β
ββ triggerJob(jobId) ββ POST /api/2.1/jobs/run-now
ββ repairRun(runId, taskKeys) ββ POST /api/2.1/jobs/runs/repair
ββ cancelRun(runId) ββ POST /api/2.1/jobs/runs/cancel
β
ββ pensarFix(runId, taskKey, lang, userHint)
β ββ (optional userHint injected into both LLM passes via _buildHintBlock)
β ββ LLM decides which DESCRIBE TABLE to run
β ββ /api/1.2/contexts/create + /api/1.2/commands/execute (Hive Metastore)
β ββ LLM proposes find β replace with real schema
β
ββ aplicarFix(path, find, replace)
β ββ /api/2.0/workspace/export β replace β /api/2.0/workspace/import
β
ββ getFavorites() / toggleFavorite(jobId) β UserProperties
ββ getFixHistory() β UserProperties
ββ getConfig() / saveConfig(config) β UserProperties 'bedbricks_config'
ββ getServingEndpoints() β /api/2.0/serving-endpoints (READY only)
ββ setPinnedJob(jobId, jobName) / clearPinnedJob()
β
ββ doGet() β Index.html
[Index.html β mobile UI]
ββ Header: logo + dark/light toggle + β» auto-refresh countdown
ββ Tab Jobs: pinned job widget + search + filters + job list with β
π
ββ Tab Runs: global run history
ββ Tab Fixes: applied patches log
ββ Tab Settings (β): connection, LLM endpoint, pinned job
ββ Detail view: status + run history + Cancel + AI diagnosis + Repair + Think Fix
All data lives in Google's UserProperties β per-user, cross-device, no external database.
| Key | Contents | Limit |
|---|---|---|
bedbricks_config |
{active, lang, workspaces: [{name, host, token, llm_endpoint, pinned_job_id, pinned_job_name}]} |
β |
repair_<runId> |
last repair_id for chained repairs |
β |
favorites |
JSON array of job IDs | β |
fix_history |
[{ts, notebook_path, find_preview, replace_preview}] |
50 (FIFO) |
By default, anyone with the link can access the app. To restrict to specific emails, add ALLOWED_EMAILS in Apps Script β Project Settings β Script Properties:
Key: ALLOWED_EMAILS
Value: alice@example.com,bob@example.com
Leave empty (or don't add it) to allow any Google account.
You need: a Databricks workspace + personal access token (dapi...) and a Google account. Everything else depends on which install method you choose.
First-time deployment (any method): after the code is in Apps Script you must create the Web App once manually β see Create the Web App below.
The simplest path. Just copy two files into the Apps Script editor.
- Go to script.google.com β New project β rename it to
Bedbricks - Delete the default
Code.gscontent. Paste the contents ofapps_script/codigo.gs - File β New β HTML file β name it
Indexβ paste the contents ofapps_script/Index.html - File β New β Script file (optional) or edit
appsscript.jsonvia Project Settings β Show appsscript.json β pasteapps_script/appsscript.json - Save β proceed to Create the Web App
Google's official CLI. Handles auth automatically β no Cloud Console setup needed.
npm install -g @google/clasp
clasp login # opens browser for Google OAuth
clasp create --title "Bedbricks" --type webapp --rootDir apps_script
clasp pushThen proceed to Create the Web App.
Re-deploying after changes:
clasp push
No CLASP, no Node. Pure Python β useful for scripting or CI.
One-time setup:
- Create a Google Cloud project at console.cloud.google.com
- APIs & Services β Credentials β Create credentials β OAuth 2.0 Client ID β Application type: Desktop App
- Enable the Google Apps Script API for your project
- Copy Client ID and Client Secret
cp deploy_config.example.py deploy_config.py
# Fill in: SCRIPT_ID (from script.google.com β Project Settings)
# CLIENT_ID and CLIENT_SECRET (from step above)Authenticate (first time only):
python deploy_apps_script.py --authOpens a browser for Google OAuth. Token saved to ~/.apps_script_token.json.
Deploy:
python deploy_apps_script.pyThen proceed to Create the Web App. After the first deployment, add DEPLOYMENT_ID to deploy_config.py and re-run to keep it updated automatically.
This step is the same regardless of install method. Do it once in the Apps Script editor:
- Deploy β New deployment β Type: Web App
- Execute as: User accessing the web app β important (see note below)
- Who has access: Anyone within
<your org>(or "Anyone with a Google Account") - Deploy β copy the Web App URL
Open the URL on your phone. The setup wizard will ask for your Databricks host and token β that's it.
β Execute as β must be "User accessing the web app". Bedbricks stores each person's host/token/workspaces in their own
UserProperties, which is keyed to the effective user. If you deploy with "Execute as: Me", every visitor runs as you and shares your token and workspaces β the per-user model breaks. "User accessing" makes each person run as themselves (their own token, isolated). The trade-off: each user authorizes the OAuth scopes once on first open (a normal Google consent screen).This setting is UI-only. The
webapp.executeAs/accessvalues inappsscript.jsonare not applied when you create/update a deployment via the API orclaspβ Google fixes them from the dialog at deployment-creation time. Set them in Deploy β New deployment (or Manage deployments β Edit) in the browser.
Updating (after code changes): use Deploy β Manage deployments β Edit β New version instead of creating a new deployment, so your URL stays the same. (Changing Execute as on an existing deployment is often locked β if so, create a New deployment, which yields a new URL.)
Think Fix requires a Databricks Model Serving endpoint. If you have one, you can select it from the Settings tab (β) β it's populated automatically from your workspace. The app works fully without it; Think Fix is just hidden.
bedbricks/
βββ README.md
βββ deploy_apps_script.py β deploy via Apps Script API (no CLASP needed)
βββ deploy_config.example.py β template: copy to deploy_config.py
βββ deploy_config.py β gitignored β your Script ID + Deployment ID
βββ .gitignore
βββ apps_script/
β βββ appsscript.json β manifest: OAuth scopes, runtime V8
β βββ codigo.gs β backend: GAS functions
β βββ Index.html β frontend: mobile UI, embedded logo
βββ tests/
βββ test_logic.js β 78 pure-logic tests (no GAS APIs required)
node tests/test_logic.js
# 141 passed, 0 failedTests cover all pure helpers: _parseConfig, _isConfigComplete, _parseServingEndpoints, _buildLlmPayload, _parseLlmResponse, _parseFailedTask, _parseFailedTasks, _buildRepairPayload, _parseFavorites, _toggleFavoriteLogic, _buildCancelPayload, _appendFixLog, _parseRunHistory, _extractTaskMeta, _getTaskChipType, _computeFlakiness, _gestureDelta, _buildManifest, _buildRunComparison, _globalSearch, _paramsToMap, _buildRunNowPayload, _buildHintBlock, _mergeRunPages, _runPageUrl, _dedupTasksByKey, _isSentinelTask, _sortFailedTasks, _sentinelKeys, _childRunId, _mergeJobParams, _countsAreSubjobs.
| Date | Version | Change |
|---|---|---|
| 2026-06-04 | v1 | Initial: TDD scaffold, full GAS backend, dark mode frontend, deploy script |
| 2026-06-04 | v2βv7 | Remote control: dashboard, job detail, trigger. Tab Jobs, Tab Runs, detail view. |
| 2026-06-04 | v8βv12 | repairRun() with auto latest_repair_id. Timezone. Refresh button. |
| 2026-06-04 | v13βv18 | pensarFix() β LLM + Hive Metastore β concrete find β replace. |
| 2026-06-04 | v19βv22 | Multi-task failures, sub-jobs, Repair All. |
| 2026-06-04 | v23βv25 | Databricks-inspired theme: CSS vars, square dots, bordered badges. |
| 2026-06-05 | v26 | Widget, auto-refresh 30s, filters, favorites, cancel run, run history, Fixes tab. |
| 2026-06-05 | v27βv30 | Bedbricks rebrand: embedded logo, dark/light SVG toggle. |
| 2026-06-05 | v31βv33 | Multi-user: per-user config, setup wizard, Settings tab (β), Pin Job (π), configurable LLM endpoint, English UI, marquee for long names. |
| 2026-06-05 | v42 | Full i18n EN/ES: STRINGS dict + t(key) + setLang(), data-i18n attrs, persisted in UserProperties. LLM prompts bilingual. |
| 2026-06-05 | v43 | Diff visual (LCS), Tab Clusters (start/terminate), Scheduler one-shot (GAS triggers), Perf chart (SVG), Task DAG (SVG). |
| 2026-06-05 | v44 | Replace emoji icons (clusters tab, schedule btn) with minimalist SVG icons. |
| 2026-06-05 | v45 | Critical fix: detail view stuck on "Loading..." for any run. Root cause: var t = data.tasks[i] inside renderDetail() hoisted t, shadowing the global i18n t() β TypeError: t is not a function in the success handler (silent). Fix: rename loop var t β task. |
| 2026-06-05 | v46βv49 | Logo doubled in visible size (58px β 96px); header switched to height:auto to fit the logo. |
| 2026-06-09 | v50 | PWA installable (manifest + iOS/Android meta tags). Swipe gestures: tab navigation, swipe-row quick actions (βΆ trigger / β favorite), pull-to-refresh. Per-task flakiness score: % badge in task list and DAG (red >20%, amber 10β20%). |
| 2026-06-09 | v51βv52 | Task Code Viewer: { } code / β subjob chips per task row. Notebook tasks: 30-line preview with fade + expand to full code (on-demand workspace/export). run_job tasks: panel with referenced job name + direct navigation. _extractTaskMeta() + getNotebookPreview(path, full). Light mode as default theme. 51 tests. |
| 2026-06-09 | v53 | Light-mode fix: diagnosis card strong and inline code used fixed dark colors (invisible on light bg). Fixed to var(--text) / explicit light color. |
| 2026-06-10 | v54 | Drill into failed sub-jobs: a failed run_job task now shows an "open child job β" button that navigates to the child run. _parseFailedTasks captures subjob_id; getJobDetail resolves subjob_name. |
| 2026-06-10 | v55 | Repair fixes: rerun_tasks was sent nested ([["task"]]) β 400 MALFORMED_REQUEST (Repair was fully broken). Now a flat array + rerun_dependent_tasks:true (the DAG continues). latest_repair_id persisted in UserProperties (the API never returns repair_history). Verified against live Databricks. |
| 2026-06-10 | v56 | Critical fix β blank FAILED screen: flakiness (declared in renderDetail) was used inside renderFailedUI (a separate function) β ReferenceError β the whole FAILED UI failed to render for jobs with a DAG of β₯2 tasks. Broken since v50. Fix: pass flakiness as a parameter. |
| 2026-06-10 | v57 | V6 β Power features: parameter editor (key-value modal β notebook_params), global search (header overlay: jobs/runs/IDs), run comparison (two-run picker + per-task duration table with delta). 69 tests. |
| 2026-06-10 | v58 | Fix iOS auto-zoom: search and params inputs to font-size:16px (the full-screen overlay pushed Back off-screen). |
| 2026-06-10 | v59 | Fix run comparison "?": _fetchRunsWithTasks used the 2.1 endpoint (400 on large runs) and parsed the error body as an empty run. Now 2.2 + HTTP-code check + fallback. Also fixes flakiness on large jobs. |
| 2026-06-10 | v60 | Comparison table table-layout:fixed + Task column with ellipsis (no longer overflows the screen). |
| 2026-06-10 | v61βv62 | Per-job status dots: runs/list?job_id&limit=5 per job in parallel (mirrors Databricks' last 5). Reverted global limit=100 β 25 (the endpoint max). |
| 2026-06-10 | v63 | Universal UI: loading spinner, icon set (βΆ β β βΉ), fixed hard-coded translations (Repair, Task Code Viewer, Install) + data-i18n-title for localized tooltips. |
| 2026-06-10 | v64 | π§ Repair / π‘ Think Fix with text labels (more intuitive); sun icon with a filled center (no longer mistaken for the settings gear); bilingual AI diagnosis (ES/EN prompt + getJobDetail(jobId, lang)). |
| 2026-06-10 | v65 | Smarter Think Fix for missing tables: searches the metastore (SHOW TABLES) for the correct one, or comments out the line as a last resort (never an empty fix). |
| 2026-06-10 | v66 | Fix: switchTab did not close the detail-view overlay β it stayed underneath when changing tabs. |
| 2026-06-10 | v67 | Detailed Fixes log: expandable entries with full red/green diffs; backend stores find/replace (cap 800) + a UserProperties size guard. |
| 2026-06-10 | v68 | Multi-workspace: config refactored to {active, lang, workspaces[]} with automatic migration; add/edit/delete/switch from Settings; _getConfig_() returns the active workspace flattened (no call-site changes). 78 tests. |
| 2026-06-10 | v69 | Removed model-specific labels: AI diagnosis card now reads "AI Diagnosis" / "DiagnΓ³stico IA" (the endpoint is configurable). |
| 2026-06-10 | v71 | Log out / reset button in Settings β resetConfig() clears all saved workspaces & tokens and returns to the setup wizard. |
| 2026-06-10 | v72 | Setup screen polish: corrected tagline, Connect button as a single arrow, and trimmed the logo (was 480Γ251 with ~64% transparent margin β cropped to content) so it renders crisp and the layout is balanced. |
| 2026-06-11 | v73 | Think Fix hint: tapping π‘ Think Fix now opens an optional free-text field β give the AI a suggestion (e.g. "the table was renamed to X") before it diagnoses. The hint is injected into both LLM passes (schema discovery + fix) and bilingual; the system prompt weighs it heavily but still validates against the real schemas. Empty hint = fully automatic, same as before. New pure helper _buildHintBlock(hint, lang) + 7 tests (81 total). |
| 2026-06-11 | v74 | Public-repo hygiene: replaced internal example identifiers with neutral ones (analytics.customers_v2, generic job IDs) across the hint placeholder and test_logic.js fixtures, so nothing real-world ships in the open-source release. No behaviour change. |
| 2026-09-01 | v79 | runs/get was never paginated. The API caps at 100 tasks per page; on an orchestrator job with hundreds of tasks only page 1 was read, so the only FAILED task Bedbricks could see was the sentinel (the single DAG leaf, which lands on page 1) β Think Fix always diagnosed its useless traceback while the real failures sat on page 2. Fixed in all three readers (getJobDetail, _fetchRunsWithTasks, getOrchestratorStatus; the last one also moved off API 2.1, which returns 400 Resources with more than 100 tasks... β swallowed by a catch, so the widget progress bar had always read 0/0). Sentinel tasks are now detected, sorted last, shown with a sentinel β not the cause chip and skipped for diagnosis/Think Fix/Repair. tasks also carries one entry per attempt, not per task (after repairs, roughly twice as many entries as tasks) β deduped by task_key keeping the last attempt. 95 tests. |
| 2026-09-06 | v81 | The v79 pagination fix reached 3 of the 4 readers β pensarFix() was the one left behind. Think Fix kept calling runs/get unpaginated, so on a >100-task orchestrator the actually-failing task (page 2) was never found: it silently fell through with notebookPath = null, the LLM diagnosed with an empty notebookContent, and the findβreplace it invented could never match β surfacing as "the code to replace was not found in the notebook", which reads like a notebook problem and is really a paging one. The paginated helper (_getRunDetailPaged) already existed and every other reader used it. Fixed by extracting _findTaskInRun() β paginate and dedupe by task_key keeping the last attempt, since with retries the first entry carries the wrong traceback. Now fails loudly when the task isn't in the run instead of diagnosing blind: a diagnosis without code is a made-up patch. 6 regression tests (101). |
| 2026-09-06 | v82 | Same bug, the other endpoint: jobs/get was still on API 2.1. 2.1 does not degrade β it returns 400 Resources with more than 100 tasks can only be handled by API 2.2, and a catch turned that into "no data" silently: getJobParams() returned {} on any >100-task job, so the custom Run Now parameter editor was always empty. Switching to 2.2 alone is not enough either β it answers 200 but truncates settings.tasks to 100 (measured on a real job: only a fraction of the tasks that carry base_parameters land on page 1). Added _getJobDetailPaged() / _mergeJobPages(), which accumulate settings.tasks across pages while keeping the page-1-only top-level fields (name, parameters, job_clusters). Both 2.1 callers migrated (getJobParams, sub-job name lookup); zero raw runs/get or jobs/get calls left in the file. 6 more tests (107). |
| 2026-09-06 | v83 | Think Fix sent the LLM "the first N characters" of the notebook β the worst possible heuristic, because the error is almost never at the top. Measured on a real notebook: ~21k chars, with the failing cell starting just past the 10,000-char cut, so even after v81 handed the LLM real code, it was the wrong half. Replaced by _selectRelevantCode(): split the notebook on Databricks' # COMMAND separators, pull anchors from the traceback (ANSI colour stripped first, or roster and mroster count as two), score each cell by how many rare anchors it holds (a pocket tf-idf β spark in a PySpark notebook distinguishes nothing), and emit the winning cells in original order, verbatim, with explicit [ ... N cells omitted ... ] markers; the system prompt now forbids spanning a marker in find. A cell bigger than the budget is cropped around its anchor, not from the top. Two defects the real-data check caught and the unit tests hadn't: adjacent chosen cells were glued without their # COMMAND ----------, producing text that exists nowhere in the notebook (so find could never match β the very bug being fixed); and the gap markers weren't counted against the budget (144 chars returned for a 140 budget). Result on that notebook: the failing cell now makes it in at both the 3,000 and 8,000 budgets, and every emitted section is verbatim. 13 more tests (120), including a budget sweep over 77 sizes and a guard that the inline test copy hasn't drifted from codigo.gs. |
| 2026-09-07 | v84 | A job whose tasks are all sub-jobs β a "director" β was the one shape where every safety net missed at once. Four independent defects, all of them silent. (1) Sentinel detection was by name. _isSentinelTask matched the substrings centinela/sentinel; rename the task and it becomes invisible. On a director that is not cosmetic: every other failed task is a run_job, and run_job tasks are deliberately not diagnosed β so the undetected sentinel became the only task with an AI diagnosis, a Think Fix button and a slot in Repair, i.e. exactly the v79 bug re-entering through a rename. Replaced by a structural rule β the DAG leaf whose run_if is ALL_DONE with β₯2 dependencies β calibrated against a real multi-hundred-task DAG where it isolates the sentinel with zero false positives (of its ALL_DONE tasks only the sentinel is a leaf; the wide gate has dozens of dependents, so it is not one). The name match is kept as an OR, never as the only signal. (2) Drilling into a failed sub-job opened the wrong run. The button called showDetail(job_id) β runs/list β runs[0], the child's latest run β a different run whenever you open an older parent run from the history, after a repair, or after someone re-ran the child by hand, with nothing on screen saying so. The child run id is not in runs/get (run_job_output is null there, measured): it comes from runs/get-output on the parent task's run_id. New getRunDetail(runId) + showRunDetail() navigate to that exact run, falling back to the job with a different tooltip when it cannot be resolved. (3) Repair silently changed meaning. Repairing a run_job task re-runs the entire child job, not the task that failed inside it β a whole wave. Now it confirms first. (4) The parameter editor was empty and the progress bar mislabelled. getJobParams only merged notebook_task.base_parameters; a director has none, and its parameters live in job-level settings.parameters (which run_job_task does propagate) β same symptom v82 fixed, different cause. And the bar counted waves while calling them "tasks", off by more than an order of magnitude. Plus a generalized sync guard: 36 pure helpers live duplicated between codigo.gs and the tests, and the old guard covered one block. SYNC2/SYNC3 now compare every shared helper modulo comments β which surfaced 5 pre-existing drifts where the inline copy certifies a superseded version (_buildFixLogEntry, _appendFixLog, _buildLlmPayload, _parseConfig, _parseLlmResponse), documented so the list can only shrink. 21 more tests (141). |
| 2026-06-12 | v78 | Think Fix no longer auto-focuses the hint textarea (the mobile keyboard popped up on every tap). The hint field just reveals + scrolls into view; the keyboard opens only if the user taps it β the hint is optional. |
| 2026-06-12 | v77 | After a successful Apply Fix, that task's Think Fix button also locks (disabled) β not just the apply button β so a patched notebook can't be re-diagnosed by accident. |
| 2026-06-12 | v76 | Code-error warning banner reworked: dropped the stray ! text prefix, added a proper β icon in a flex layout (icon aligned to the first line), and reworded the message to point at Think Fix (EN/ES). |
| 2026-06-12 | v75 | Three fixes. (1) Per-task AI diagnosis: when a job fails with several FAILED tasks, each task now gets its OWN error trace + its OWN diagnosis card (before, only the first task was diagnosed). getJobDetail diagnoses per task; renderFailedUI renders one diagnosis card per failed task (+ its own raw log and Think Fix). (2) Removed swipe-to-switch-tabs: the horizontal tab-swipe gesture hijacked horizontal scroll when reading full notebook code in the detail view β removed; pull-to-refresh and swipe-on-a-row quick actions stay. (3) Apply-fix button locks: each Think Fix panel owns its apply button (unique id + per-panel fix in _fixByPanel) and stays disabled after a successful apply β no more repeat taps (previously all panels shared btn-apply-fix/_pendingFix, so in multi-task only the first button disabled). |
