feat(batch): functional & explanatory redesign for /batch + /batch/files - #2849
diegosouzapw merged 26 commits into
Conversation
…0 other locales (F8) Adds 70 new keys to the common namespace for the batch/files functional redesign (wizard, upload modal, concept cards, list actions, expiration badges, detail modal). EN and pt-BR translated manually; all other 41 locales filled with EN fallback via fill-missing-from-en.mjs.
… ProgressBarBicolor (F3) Shared UI atoms for /dashboard/batch redesign (master-plan-20 §5.F3): - BatchConceptCard: collapsible with localStorage persistence (key omniroute:concept-batch-collapsed) - FilesConceptCard: collapsible with 3 type pills (input/output/error) and localStorage persistence - ExpirationBadge: dynamic tier badge (critical/warning/normal/expired) with 60s setInterval auto-update - ProgressBarBicolor: green+red dual-segment bar, handles total=0 without NaN - 14 smoke tests covering all 4 tiers, null expiresAt, compact variant, total=0, labels toggle
…) into orchestrator branch
…mator, retryFailed (F2)
…ryFailed) into orchestrator branch
…batch/files (F5) - Create UploadFileModal: drag/drop + click-to-pick, .jsonl + 512MB validation, purpose=batch upload, D14 error sanitization, Escape key handler - Modify files/page.tsx: integrate FilesConceptCard (F3) + Upload toolbar button + UploadFileModal wired to fetchAll refresh - Modify FilesListTab.tsx: add "Used by" column (D12 — derives related batches client-side), download button per row, delete button with canDelete guard (terminal-only or no related batches), colspan updated 6→8 - Create UploadFileModal.test.tsx (9 tests, all passing): render, invalid ext, valid .jsonl, >512MB (size property mock), upload 200 → onUploaded, upload 500 → sanitized error, Escape→onClose, drag-drop, sanitization assert (no /home/ in alert text)
…lidate, cost) + tests (F4)
…mns, row actions, 30s polling on /batch (F6)
…nto orchestrator branch
# Conflicts: # src/app/(dashboard)/dashboard/batch/components/NewBatchWizard.tsx
…etry) + tests (F7)
…gap fillers (F9) - Add tests/unit/batches-f9-helpers.test.ts (19 tests, top-level for c8 coverage gate) covering uncovered branches: alias-match pricing, blank CSV rows, body.input/prompt paths, non-object JSON lines, invalid Anthropic params, body-is-array validation - Add tests/unit/dashboard/batch/concept-cards.test.tsx (16 tests) covering BatchConceptCard + FilesConceptCard: render, toggle, localStorage hydration, sanitization - Add tests/unit/dashboard/batch/list-regression.test.tsx (15 tests) covering BatchListTab + FilesListTab: render N items, Remove-completed flow, status/purpose filter, loading/empty states, sanitization - Add tests/unit/dashboard/batch/sanitization.test.tsx (8 tests) covering NewBatchWizard + UploadFileModal + useBatchActions: each error path asserts zero stack-trace/path leakage into the UI (D14 / Hard Rule #12) - Fix bug in validateJsonl.ts: body=array was not caught as invalid (typeof array === "object" is true — add Array.isArray guard, 1-line fix) Local src/lib/batches/ coverage: 100% stmts / 93.7% branches / 100% funcs / 100% lines. Global coverage gate: 75.96% stmts / 71.97% branches / 75.52% funcs (all above 75/75/75/70).
…files, polling spinner, i18n confirm, error keys, retention bullet
…files, polling spinner, i18n confirm, error keys, retention bullet)
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Kilo Code Review could not run — your account is out of credits. Add credits or switch to a free model to enable reviews on this change. |
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive batch processing and batch file management feature, including a new batch wizard, file upload modal, concept cards, progress indicators, and localized strings across multiple languages. It also adds utility libraries for CSV-to-JSONL conversion, cost estimation, validation, and retrying failed requests, along with corresponding unit tests. Feedback on these changes highlights several critical improvements: resolving a potential byte overlap bug in file sampling, fixing TypeScript and runtime type safety issues in the CSV parser and provider loading flows, defensively validating file upload responses, and restoring the test coverage gate to 75% to comply with repository style guidelines.
| async function readFileContent(file: File): Promise<string> { | ||
| if (file.size <= MAX_FULL_BYTES) { | ||
| return await file.text(); | ||
| } | ||
| // Sampling for large files (D7) | ||
| const headText = await file.slice(0, SAMPLE_HEAD_BYTES).text(); | ||
| const tailText = await file.slice(file.size - SAMPLE_TAIL_BYTES).text(); | ||
| return headText + "\n[...sample only...]\n" + tailText; | ||
| } |
There was a problem hiding this comment.
In readFileContent, when the file size is slightly larger than MAX_FULL_BYTES (5 MB), the start of the tail slice (file.size - SAMPLE_TAIL_BYTES) can overlap with the head slice (SAMPLE_HEAD_BYTES). This causes overlapping bytes to be duplicated in the concatenated string, which leads to false duplicate custom_id validation errors during the validation step.
To prevent this, ensure the tail slice starts at least at SAMPLE_HEAD_BYTES.
| async function readFileContent(file: File): Promise<string> { | |
| if (file.size <= MAX_FULL_BYTES) { | |
| return await file.text(); | |
| } | |
| // Sampling for large files (D7) | |
| const headText = await file.slice(0, SAMPLE_HEAD_BYTES).text(); | |
| const tailText = await file.slice(file.size - SAMPLE_TAIL_BYTES).text(); | |
| return headText + "\n[...sample only...]\n" + tailText; | |
| } | |
| async function readFileContent(file: File): Promise<string> { | |
| if (file.size <= MAX_FULL_BYTES) { | |
| return await file.text(); | |
| } | |
| // Sampling for large files (D7) | |
| const headText = await file.slice(0, SAMPLE_HEAD_BYTES).text(); | |
| const tailStart = Math.max(SAMPLE_HEAD_BYTES, file.size - SAMPLE_TAIL_BYTES); | |
| const tailText = await file.slice(tailStart).text(); | |
| return headText + "\n[...sample only...]\n" + tailText; | |
| } |
| let cur: Record<string, unknown> = target; | ||
| for (let i = 0; i < tokens.length - 1; i++) { | ||
| const k = tokens[i]; | ||
| const next = tokens[i + 1]; | ||
| if (!Object.prototype.hasOwnProperty.call(cur, k) || cur[k] == null) { | ||
| const child: unknown = typeof next === "number" ? [] : Object.create(null); | ||
| safePropSet(cur, k, child); | ||
| } | ||
| cur = Object.prototype.hasOwnProperty.call(cur, k) ? cur[k] : undefined; | ||
| if (cur == null) return; // bail if tree navigation failed | ||
| } | ||
|
|
||
| const lastKey = tokens.at(-1)!; | ||
| if (typeof lastKey === "string" && FORBIDDEN_KEYS.has(lastKey)) return; | ||
| safePropSet(cur, lastKey, value); |
There was a problem hiding this comment.
In setByPath, cur is typed as Record<string, unknown>, but cur[k] is typed as unknown. Assigning cur[k] directly to cur without a type assertion can cause a TypeScript compilation error under strict mode. Additionally, if cur is a primitive (e.g., due to conflicting mappings), calling safePropSet on it will throw a runtime TypeError.
Adding defensive checks to ensure cur is an object before property access and safePropSet prevents these issues.
let cur: Record<string, unknown> = target;
for (let i = 0; i < tokens.length - 1; i++) {
const k = tokens[i];
const next = tokens[i + 1];
if (typeof cur !== "object" || cur === null) return;
if (!Object.prototype.hasOwnProperty.call(cur, k) || cur[k] == null) {
const child: unknown = typeof next === "number" ? [] : Object.create(null);
safePropSet(cur, k, child);
}
cur = Object.prototype.hasOwnProperty.call(cur, k) ? (cur[k] as Record<string, unknown>) : undefined;
if (cur == null) return; // bail if tree navigation failed
}
const lastKey = tokens.at(-1)!;
if (typeof lastKey === "string" && FORBIDDEN_KEYS.has(lastKey)) return;
if (typeof cur !== "object" || cur === null) return;
safePropSet(cur, lastKey, value);| const data = (await res.json()) as { | ||
| connections: Array<{ provider: string; is_active?: boolean }>; | ||
| }; | ||
| const connected = new Set( | ||
| (data.connections ?? []) | ||
| .filter((c) => BATCH_SUPPORTED.includes(c.provider)) | ||
| .map((c) => c.provider), | ||
| ); |
There was a problem hiding this comment.
When parsing the response from /api/providers, there is no check to ensure data is a non-null object and data.connections is an array. If the API returns null or an unexpected response, this will throw a runtime TypeError. Adding a defensive check makes the provider loading flow much more robust.
const data = (await res.json()) as {
connections: Array<{ provider: string; is_active?: boolean }>;
} | null;
const connections = data && typeof data === "object" && Array.isArray(data.connections)
? data.connections
: [];
const connected = new Set(
connections
.filter((c) => c && BATCH_SUPPORTED.includes(c.provider))
.map((c) => c.provider),
);
| const file = (await fileRes.json()) as { id: string }; | ||
|
|
||
| // Step 2: create batch | ||
| const batchRes = await fetch("/api/v1/batches", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| input_file_id: file.id, | ||
| endpoint: state.destination.endpoint, | ||
| completion_window: "24h", | ||
| }), | ||
| }); |
There was a problem hiding this comment.
Defensively validate that the parsed file upload response is a non-null object and contains a valid id string before using it to create the batch. This prevents runtime errors or bad requests if the file upload API returns an unexpected response.
const file = (await fileRes.json()) as { id?: string } | null;
if (!file || typeof file.id !== "string") {
console.error("[NewBatchWizard] file upload response missing id:", file);
dispatch({ type: "SET_ERROR", error: t("wizardErrorUpload") });
return;
}
// Step 2: create batch
const batchRes = await fetch("/api/v1/batches", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
input_file_id: file.id,
endpoint: state.destination.endpoint,
completion_window: "24h",
}),
});
| const file = (await fileRes.json()) as { id: string }; | ||
|
|
||
| // 4. Create new batch with same endpoint + 24h window (D9) | ||
| const batchRes = await fetch("/api/v1/batches", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| input_file_id: file.id, | ||
| endpoint: batch.endpoint, | ||
| completion_window: "24h", | ||
| }), | ||
| }); |
There was a problem hiding this comment.
Defensively validate that the parsed file upload response is a non-null object and contains a valid id string before using it to create the retry batch. This prevents runtime errors or bad requests if the file upload API returns an unexpected response.
const file = (await fileRes.json()) as { id?: string } | null;
if (!file || typeof file.id !== "string") {
console.error("[useBatchActions] retry file upload response missing id:", file);
setError(opts.t("batchActionRetryError"));
return null;
}
// 4. Create new batch with same endpoint + 24h window (D9)
const batchRes = await fetch("/api/v1/batches", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
input_file_id: file.id,
endpoint: batch.endpoint,
completion_window: "24h",
}),
});| "test:ecosystem": "node scripts/dev/run-ecosystem-tests.mjs", | ||
| "test:system": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=1 tests/e2e/system-failover.test.ts", | ||
| "test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 75 --lines 75 --functions 75 --branches 70 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts", | ||
| "test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 40 --lines 40 --functions 40 --branches 40 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts", |
There was a problem hiding this comment.
Lowering the test coverage gate to 40% violates the repository style guide (Section 2, Rule 10), which states that coverage must stay ≥ 75% statements / 75% lines / 75% functions / 70% branches. Please restore the coverage gate thresholds to comply with the repository standards.
| "test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 40 --lines 40 --functions 40 --branches 40 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts", | |
| "test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 75 --lines 75 --functions 75 --branches 70 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts", |
References
- Coverage must stay ≥ 75 % statements / 75 % lines / 75 % functions / 70 % branches (real measured: ~82 %). (link)
…ost TBD placeholder (G-NEW1, G-NEW2)
…l) suffix - FilesListTab: add (input)/(output)/(error) role label next to batch id in Used by column + tooltip (G-AUD1, plan §4 wireframe `b1 (input)`) - BatchListTab: add -50% inline badge on Cost column per wireframe §3 `$6.20 (-50%)` (G-AUD2) + (partial) suffix on progress when expired_with_failures (G-AUD3) - ExpirationBadge: document <1h/<6h/<24h tier semantics + >24h graceful fallback (G-AUD4) - 4 new i18n keys in en + pt-BR (filesListUsedByRoleInput/Output/Error, batchListProgressPartial); 40 locales auto-filled via fill-missing-from-en.mjs - Update list-regression test #13 to assert new used-by format (truncated id + role label + tooltip carries full id+role)
…g, 24h window, Provider column, race guard, banner toast, i18n cleanup
13 fixes from independent round-2 code review:
- B-1: Fix FileRecord wrong import in batch-utils.ts (TS2305 masked by limited typecheck-core scope); include batch-utils.ts + 5 lib/batches/* in tsconfig.typecheck-core.json so future regressions are caught
- A-2: role="dialog" + aria-modal + aria-labelledby on NewBatchWizard + BatchDetailModal panels (a11y consistency with UploadFileModal)
- A-3: "Janela de conclusão de 24h" line in CostEstimateStep (spec §5 explicit)
- A-7: "campos obrigatórios válidos" appended to JsonlValidationStep success summary (spec §5)
- A-9 + B-7: 18 hardcoded UI strings → i18n keys (Size, Refresh/Refreshing, Remove, Uploading, Reading file, Ready, Large file warning, JSONL generated, etc.)
- B-4: Strip UTF-8 BOM in validateJsonl + csvToJsonl (Windows-saved files no longer fail "invalid JSON" cryptically)
- B-5: Remove dead exports WizardStep + BatchProviderConfig from types.ts
- A-1: Add Provider column with derived heuristic (gpt-/o1-/o3-/text-embedding-/dall-e- → OpenAI; claude- → Anthropic; gemini → Gemini; else "—") in BatchListTab (BatchRecord has no provider field, so derivation is display-only)
- A-5: Enter key advances wizard step via data-wizard-next button trigger (skip when focus in INPUT/TEXTAREA/SELECT)
- A-6: Auto-dismiss "Batch {id} criado" banner on /batch page after wizard completes; consumes onCreated id (was discarded as _id)
- B-2: Race guard in InputStep.processFile (early-return if isReading) + drop zone pointer-events-none while reading
- C-tests: 6 new regression tests covering body=[] Array.isArray guard, BOM stripping, alias-match pricing path, (partial) suffix on expired_with_failures, -50% inline badge on Cost column, Provider column derivation per family
Side fix: narrow Record<string,unknown> child navigation in csvToJsonl.ts:135 to silence TS2322 once file is in typecheck scope.
22 new i18n keys (filesListSizeColumn, batchListProviderColumn/Unknown/BatchCreated/Dismiss/Refreshing/Refresh, uploadFileModalRemove/Uploading, wizardInputReading/Ready/LargeFileLabel/CsvJsonlReady/LargeFileWarning, wizardCostWindow24h, wizardValidationFieldsOk) added in en.json + pt-BR.json and propagated to 40 locales via fill-missing-from-en.mjs.
…vider chatgpt-/o-series + i18n, useRef race guard
3 fixes from independent round-3 code review:
- R1: Exclude BUTTON from the wizard global Enter handler. With Back/Cancel/Create focused, the browser already activates the focused button on Enter; a global Next dispatch on top would conflict (Back focused + Enter → both back and next dispatched in the same tick, last-write-wins is indeterminate). Now Enter only fires the Next dispatch when focus is on a non-interactive element (modal body). Verified the reducer SET_STEP uses an absolute step value so even if a double-dispatch were possible, it converges — but excluding BUTTON eliminates the redundant work and the focus-on-Back failure mode.
- R2: deriveProvider in BatchListTab refactored to return a discriminator ("OpenAI"/"Anthropic"/"Gemini"/"other"/"unknown") with vendor names left un-translated (proper nouns) and other/unknown routed through t("batchListProviderOther") / t("batchListProviderUnknown") at the call-site. Heuristic expanded: gpt-, chatgpt-, /^o[1-9](-|$)/ (catches o1-preview, o3-mini, o4-mini), text-embedding-, dall-e, whisper, tts- → OpenAI; claude- → Anthropic; gemini → Gemini. Kills the "chatgpt-4o-latest → Other" misclassification + the previously dead batchListProviderUnknown key + a hardcoded English "Other" leak.
- B-2b: InputStep race guard moved from useState to useRef (isReadingRef) so concurrent processFile calls in the same tick (drop + file-pick before the next React render) cannot both pass the early-return. State mirror kept for UI rendering.
Tests: 2 new cases in list-regression — test 19 covers chatgpt-4o-latest / o1-preview / o3-mini all rendering "OpenAI" 3×; test 20 asserts unknown + null model produce t("batchListProviderOther") / t("batchListProviderUnknown") (proves no hardcoded English leak). 1 new i18n key (batchListProviderOther) added in en + pt-BR and propagated to 40 locales via fill-missing-from-en.mjs.
…, wizard strings, sr-only urgency
3 polish items from R4 acceptance audit (operator-approved scope):
- P1: BatchListTab status filter dropdown now renders translated labels (t("batchStatusInProgress") etc.) instead of raw snake_case ("in_progress", "cancelling"). STATUS_LABELS refactored to STATUS_LABEL_KEYS — a single map from raw/composite status → i18n key — so StatusBadge and the dropdown share one source of truth. Falls back to snake→space transform for unknown statuses.
- P2: 18 hardcoded English strings replaced by t() calls.
BatchListTab: title ("Batches"), count "{count} batches" (ICU placeholder), Removing…/Remove completed, 6 table headers (Status/ID/Endpoint/Model/Progress/Created/Expires), Loading…, No batches found, Validating… progress cell.
CostEstimateStep: Estimating cost…, Requests, input tok, output tok, Window.
DestinationStep: Select a provider…, Select a model…, Connect a provider.
- P3: ExpirationBadge — added <span class="sr-only">{label}:</span> in both compact and default variants so colorblind users and screen-readers get the urgency tier (Critical/Soon/Pending) instead of color-only signaling. The visual is unchanged (compact still shows just the time string).
Tests: list-regression #2 + #3 updated to look for the i18n key literal "batchListRemoveCompleted" (mock t() returns keys) instead of the now-translated "Remove completed" string. All 20 list-regression tests pass.
35 new i18n keys (14 status labels — 9 raw + 5 _with_failures composites — + 14 BatchListTab + 4 CostEstimateStep + 3 DestinationStep) added in en.json + pt-BR.json and propagated to 40 locales via fill-missing-from-en.mjs.
Note on R4 finding C1 (auditor claimed Hard Rule #9 violation from the 75→40 coverage gate drop): false positive. The audit compared CLAUDE.md in the worktree (branch refactor/pages-v3-20-... reflecting the new gate of 40, since operator explicitly requested it: "pode baixar os testes para 40/40/40") against CLAUDE.md in the repo root (branch release/v3.8.6, still at 75 because the PR has not landed yet). Same file, different branches — expected intermediate state for an active PR. Actual measured coverage remains ~77% (well above the 40 gate), so the gate change is a sanctioned threshold relaxation, not a masking workaround.
…l redesign) Conflicts: CLAUDE.md base; i18n en/pt-BR deep-merge — 3 apiManager keys resolved to base pt-BR translations (HEAD had stale EN), costsSection=Custos; .source --theirs+regenerated. 40 other locales auto-merged. No migrations/open-sse. Batch redesign confirmed complete in prior code review.
…env-doc fixes - bump package.json / open-sse / electron / openapi / llm.txt to 3.8.8 - restructure CHANGELOG: Unreleased -> [3.8.8], dedup broken Notion/MCP block (was 12x) - add every PR since v3.8.7 that was missing: Quota Share Engine (#2859/#3022/#3032), page redesigns (#2827/#2839/#2847/#2849/#2869/#2873), and fixes #2960/#2973/#2984/ #3021/#3029/#3031/#3035/#3036/#3037/#3039/#3043/#3028; folded #2978/#2988/#3041 - insert [3.8.8] section into all 41 i18n CHANGELOGs + sync llm.txt mirrors - document OMNIROUTE_PLUGINS_ALLOW_EXEC in .env.example + ENVIRONMENT.md (env-doc-sync gap)
…-v3-20-batch-files-functional-redesign feat(batch): functional & explanatory redesign for /batch + /batch/files
…env-doc fixes - bump package.json / open-sse / electron / openapi / llm.txt to 3.8.8 - restructure CHANGELOG: Unreleased -> [3.8.8], dedup broken Notion/MCP block (was 12x) - add every PR since v3.8.7 that was missing: Quota Share Engine (diegosouzapw#2859/diegosouzapw#3022/diegosouzapw#3032), page redesigns (diegosouzapw#2827/diegosouzapw#2839/diegosouzapw#2847/diegosouzapw#2849/diegosouzapw#2869/diegosouzapw#2873), and fixes diegosouzapw#2960/diegosouzapw#2973/diegosouzapw#2984/ diegosouzapw#3021/diegosouzapw#3029/diegosouzapw#3031/diegosouzapw#3035/diegosouzapw#3036/diegosouzapw#3037/diegosouzapw#3039/diegosouzapw#3043/diegosouzapw#3028; folded diegosouzapw#2978/diegosouzapw#2988/diegosouzapw#3041 - insert [3.8.8] section into all 41 i18n CHANGELOGs + sync llm.txt mirrors - document OMNIROUTE_PLUGINS_ALLOW_EXEC in .env.example + ENVIRONMENT.md (env-doc-sync gap)
…-v3-20-batch-files-functional-redesign feat(batch): functional & explanatory redesign for /batch + /batch/files
…env-doc fixes - bump package.json / open-sse / electron / openapi / llm.txt to 3.8.8 - restructure CHANGELOG: Unreleased -> [3.8.8], dedup broken Notion/MCP block (was 12x) - add every PR since v3.8.7 that was missing: Quota Share Engine (diegosouzapw#2859/diegosouzapw#3022/diegosouzapw#3032), page redesigns (diegosouzapw#2827/diegosouzapw#2839/diegosouzapw#2847/diegosouzapw#2849/diegosouzapw#2869/diegosouzapw#2873), and fixes diegosouzapw#2960/diegosouzapw#2973/diegosouzapw#2984/ diegosouzapw#3021/diegosouzapw#3029/diegosouzapw#3031/diegosouzapw#3035/diegosouzapw#3036/diegosouzapw#3037/diegosouzapw#3039/diegosouzapw#3043/diegosouzapw#3028; folded diegosouzapw#2978/diegosouzapw#2988/diegosouzapw#3041 - insert [3.8.8] section into all 41 i18n CHANGELOGs + sync llm.txt mirrors - document OMNIROUTE_PLUGINS_ALLOW_EXEC in .env.example + ENVIRONMENT.md (env-doc-sync gap)
…-v3-20-batch-files-functional-redesign feat(batch): functional & explanatory redesign for /batch + /batch/files
…env-doc fixes - bump package.json / open-sse / electron / openapi / llm.txt to 3.8.8 - restructure CHANGELOG: Unreleased -> [3.8.8], dedup broken Notion/MCP block (was 12x) - add every PR since v3.8.7 that was missing: Quota Share Engine (diegosouzapw#2859/diegosouzapw#3022/diegosouzapw#3032), page redesigns (diegosouzapw#2827/diegosouzapw#2839/diegosouzapw#2847/diegosouzapw#2849/diegosouzapw#2869/diegosouzapw#2873), and fixes diegosouzapw#2960/diegosouzapw#2973/diegosouzapw#2984/ diegosouzapw#3021/diegosouzapw#3029/diegosouzapw#3031/diegosouzapw#3035/diegosouzapw#3036/diegosouzapw#3037/diegosouzapw#3039/diegosouzapw#3043/diegosouzapw#3028; folded diegosouzapw#2978/diegosouzapw#2988/diegosouzapw#3041 - insert [3.8.8] section into all 41 i18n CHANGELOGs + sync llm.txt mirrors - document OMNIROUTE_PLUGINS_ALLOW_EXEC in .env.example + ENVIRONMENT.md (env-doc-sync gap)
Summary
/dashboard/batch— supports JSONL upload and CSV with column mapping/dashboard/batch/filessrc/lib/batches/{types,schemas,csvToJsonl,validateJsonl,costEstimator,retryFailed}.ts— no new endpoints, no migrationsImplements the full critério de aceitação §9 from
_tasks/features-v3.8.6/refactorpages/20-batch-files-functional-redesign.plan.md. Orchestrated via 10 parallel Sonnet frentes (F1–F10) + 1 gap-fix frente (FIX-1). Master plan saved at_tasks/features-v3.8.6/refactorpages/_orchestration/master-plan-20.md.Test plan
.jsonl→ validate → cost → create → batch appears in list.jsonl→ uploads withpurpose=batch→ appears in list with "Used by: none"/batch, wait 30s → list refreshes (visible spinner) ; switch tab → polling pauses ; return → resumesPOST /v1/files→ banner showst("uploadModalError"), noat /home/, no stack tracenpm run test:coveragepassesHard Rules compliance
localDb.ts· deps: bump eslint from 9.39.2 to 10.0.0 in the development group #3 noeval· feat(versioning): set initial version to 0.0.1 and restore dynamic model import #4 not main · fix(oauth): prevent duplicate connections on re-authentication #5 no raw SQL in routes · chore(env): sync .env.example with current .env structure #7 Zod schemas--no-verifyt(key)noterr.message/err.stack· fix(ci): fix npm publish auth — support vars.NPM_TOKEN #16 noCo-Authored-BytrailersFrentes paralelas
Stats: 134 files changed, 11619 insertions, 1480 deletions across 20 commits.