Tags: Zipstack/unstract
Tags
UN-3185 [FIX] Restore global Prism for prismjs add-ons (Prompt Studio… … detail + HITL blank page) (#2135) * UN-3185 [FIX] Restore global Prism for prismjs add-ons (Prompt Studio detail + HITL blank page) The Vite production build tree-shakes the bare `import "prismjs"` in CombinedOutput.jsx (a side-effect-only import with no used bindings), so nothing installs the global `Prism` that `prismjs/components/prism-json` and the line-numbers plugin reference. Those add-ons then throw `ReferenceError: Prism is not defined` at module evaluation, crashing the Combined Output viewer shared by the Prompt Studio detail page and the HITL / manual-review page (both render blank). The old CRA/webpack build did not tree-shake it, so the regression only surfaces on the Vite build. Add a dedicated prismSetup module that imports Prism core with a *used* binding (survives tree-shaking) and pins it on globalThis, and import it before the add-ons in CombinedOutput so the global is guaranteed present by the time the add-on modules evaluate. Verified against a production `vite build`: the emitted chunk now runs `globalThis.Prism = <core>` immediately before `Prism.languages.json = ...`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * UN-3185 [FIX] Simplify globalThis guard in prismSetup (review) Drop the always-true `typeof globalThis !== "undefined"` check — globalThis is universally available in any ESM/Vite target. Addresses Greptile review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * UN-3185 [FIX] Install global Prism unconditionally + correct rationale (review) Address PR review: - silent-failure-hunter: a `!globalThis.Prism` guard could leave a different, pre-existing Prism in place, so the add-ons extend one instance while JsonView's highlightAll() reads another -> JSON silently unhighlighted. Assign unconditionally so the global is provably our core instance. - comment-analyzer: the prior comment blamed evaluation order yet relied on it to justify the fix. Rewrite: relying on prismjs core's self-install is unreliable under code-splitting; the explicit globalThis assignment (from first-party code imported before the add-ons) is the deterministic fix. Rebuilt: emitted chunk runs `globalThis.Prism = <core>` immediately before `Prism.languages.json = ...`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * UN-3185 [FIX] Install global Prism eagerly at app entry (fixes HITL too) Verified against the actual on-prem image (built with the manual-review plugin): the per-component prismSetup import did NOT fix it. Because manual-review's ResultEditor also imports `prismjs/components/prism-json`, Rollup hoists that add-on into a SHARED lazy chunk (PdfViewer), separate from CombinedOutput's chunk where prismSetup ran — with no ordering guarantee between two lazy chunks, so the add-on still evaluated before the global was installed. My local OSS build masked this: with no manual-review plugin, prism-json wasn't shared and stayed in CombinedOutput's chunk. Fix: import prismSetup EAGERLY from index.jsx so `globalThis.Prism` is installed at bootstrap, before any lazy chunk (including the shared add-on chunk) can load. Robust regardless of how Rollup hoists prism-json. Reproduced the shared-chunk hoist locally (two independent lazy prism-json importers) and confirmed: prism-json lands in its own lazy chunk while globalThis.Prism stays in the eager entry <script type=module>, so the global is always installed first. Fixes both the Prompt Studio detail page and the HITL review page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
UN-3185 [FIX] Deterministic CSS cascade (single bundle) + asset gzip … …+ Edit LLM Profile modal layout (#2128) * fix(frontend): bundle all CSS into one file to fix lazy-load cascade order Code-splitting (#2114) injects per-route CSS at navigation time, so equal-specificity rules across components resolve in load order — breaking the Edit LLM Profile form and Execution Logs header layout (UN-3185). cssCodeSplit:false restores a single deterministic stylesheet; JS splitting is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DsoHbMN7kTTWwcVC6NNkg * perf(frontend): gzip static assets (css/js/fonts), not just html nginx `gzip on` only compresses text/html by default, so JS/CSS shipped uncompressed. Add gzip_types so the single CSS bundle (~341KB -> ~57KB) and all JS chunks compress on the wire — offsets the cssCodeSplit:false upfront cost and speeds every asset. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DsoHbMN7kTTWwcVC6NNkg * fix(frontend): clean up Edit LLM Profile modal padding & drop dead scroll classes Remove settings-body-pad-top + add-llm-profile-scroll-root from the form root (the shared overflow-y:auto added a nested scroll context that #2119 only patched over). The sticky footer now pins directly to .conn-modal-col. Replace the dead override with a :has()-scoped padding so .conn-modal-form-pad-left is adjusted for this panel alone — sibling settings panels and the connector dialog are untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DsoHbMN7kTTWwcVC6NNkg * perf(frontend): gzip_proxied any so assets compress behind the LB nginx skips gzip for proxied requests by default (gzip_proxied off); the GKE load balancer adds a Via header to every request, so static assets shipped uncompressed despite gzip_types. Verified gzip works direct-to-pod. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DsoHbMN7kTTWwcVC6NNkg * fix(frontend): remove inner scrollbar on Edit LLM Profile modal The settings row is hard-fixed at 800px, forcing the form's column (.conn-modal-col) to scroll once Advanced Settings expands. Let the row size to the form so the modal grows instead. Scoped via :has to the LLM profile panel — the other settings panels and the connector dialog share .conn-modal-row/-col and are untouched (so the shared class stays; it is not unused). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DsoHbMN7kTTWwcVC6NNkg * docs: tighten code comments in CSS/vite/nginx changes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DsoHbMN7kTTWwcVC6NNkg * style: drop comments from AddLlmProfile.css changes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DsoHbMN7kTTWwcVC6NNkg * fix: drop pre-compressed font types from gzip_types WOFF2 (Brotli) and WOFF (zlib) are already compressed; re-gzipping wastes CPU for no size gain. application/font-woff also never matches nginx's mime.types (.woff -> font/woff), so it was a no-op. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DsoHbMN7kTTWwcVC6NNkg --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UN-3185: Pin LLM profile form submit button as a sticky footer (#2119) * UN-3185: Pin LLM profile form submit button as a sticky footer In the prompt-studio Settings modal, the LLM-profile Add/Edit form put its submit button (Update/Add) as the last element in normal flow inside a fixed 800px scroll column. When the form is tall — e.g. Advanced Settings expanded — the button flowed past the modal's visible area and appeared to overflow. - Make the button a sticky footer (position: sticky; bottom: 0) with a solid themed background so it stays pinned at the bottom of the scroll column and fields scroll beneath it. - The form root's .settings-body-pad-top declared overflow-y: auto with no bounded height — a dead nested scroll context that would stop the sticky footer from pinning to the real scroller (.conn-modal-col). Override it back to visible for this form (scoped via .add-llm-profile-scroll-root). Verified the sticky pinning against the real CSS rules; build green, biome clean. * Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Signed-off-by: Jaseem Jas <89440144+jaseemjaskp@users.noreply.github.com> --------- Signed-off-by: Jaseem Jas <89440144+jaseemjaskp@users.noreply.github.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
UN-3185: Defer heavy chunks on login route + cache fingerprinted asse… …ts (#2114) * UN-3185: Defer heavy chunks on login route + cache fingerprinted assets Fix 1 (nginx.conf): serve content-hashed /assets/* with a 1y immutable Cache-Control while keeping index.html and config/runtime-config.js non-cached, so repeat visits stop re-fetching the whole bundle. Fix 2: stop the unauthenticated /landing page from eagerly downloading the full app. Convert OSS route pages and enterprise plugin route elements to React.lazy behind a single <Suspense>, and lazy-load the app shell (PageLayout/FullPageLayout) which transitively pulled the PDF viewer, charts and lookup-studio onto /landing. New helper src/helpers/pluginRegistry.js (lazyPlugin) defers plugin chunks to navigation; OSS builds resolve the stub and fall back to NotFound, so absent-plugin routes 404 harmlessly as before. Measured /landing script requests: 201 -> 93; pdf-vendor, recharts and Monaco no longer load until navigated to. OSS-parity build (no src/plugins) verified. * UN-3185: Address review — preserve security headers, tighten plugin-absent check - nginx: drive Cache-Control from a $uri map at server scope instead of per-location add_header. Location-level add_header replaces (not merges) the inherited server headers, which dropped X-Content-Type-Options/ X-Frame-Options/Referrer-Policy/CSP from /assets, index.html and runtime-config.js. Now both locations carry no add_header and inherit all security + cache headers. - pluginRegistry: only treat the build-time stub ('Optional plugin not available') as plugin-absent; rethrow transient chunk-load failures of a shipped plugin instead of masking them as NotFound. - useMainAppRoutes: gate the OnboardProduct wrapper on PRODUCT_NAMES.unstract (the value passed as type) rather than the map being non-empty. * UN-3185: Extract shared lazyNamed helper (dedupe lazy-route boilerplate) Pull the repeated 'lazy a named export' pattern into src/helpers/lazyNamed.js and use it in Router.jsx and useMainAppRoutes.js instead of inline .then((m) => ({ default: m.X })) / a per-file 'named' helper. Behaviour is identical; /landing chunk count unchanged. Addresses cloud-PR review feedback about the duplicated helper (the two route hooks import the same util). * UN-3185: Add ErrorBoundary around lazy routes; validate lazyNamed export - Wrap the route <Suspense> in Router.jsx with the existing ErrorBoundary and a reload-prompt fallback. lazyPlugin/lazyNamed rethrow non-stub failures (e.g. a transient chunk-load blip), and App rendered <Router/> with no boundary — so such a failure would unmount the tree to a blank screen. The boundary now contains it and offers a reload (which re-fetches the chunk). - lazyNamed: throw a descriptive error when the requested named export is missing instead of handing React.lazy { default: undefined } (opaque error). Addresses greptile P1 (no ErrorBoundary) and CodeRabbit (lazyNamed validation). * UN-3185: Scope route error handling, validate lazyPlugin export, drop dead guard Review round 3: - lazyPlugin: validate the resolved export (mirror lazyNamed). A shipped plugin whose named export was renamed/removed now throws a descriptive error instead of handing React.lazy { default: undefined }; isPluginAbsent doesn't match it, so it re-throws to the ErrorBoundary rather than masking as NotFound. - ErrorBoundary: support resetKeys — clear the error when a key changes (e.g. location), so navigation recovers without a full reload. - New LazyOutlet (content-scoped <Suspense> + nav-resettable ErrorBoundary + <Outlet/>); PageLayout/FullPageLayout use it instead of a bare <Outlet/>. Per-page load spinners and chunk-load failures now stay in the content area with the shell mounted; the app-wide boundary in Router.jsx remains the backstop and is now also location-reset. Fixes the blast-radius / no-recovery and shell-blanks-on-first-nav issues from the single top-level boundary. - useMainAppRoutes: remove the now-dead 'ReadOnlyReviewPage && !ReviewLayout' warning — with lazyPlugin both are always truthy so it could never fire; the route degrades to NotFound if manual-review is absent. Build green (with and without src/plugins); biome clean. * UN-3185: Use globalThis.location.reload() in RouteLoadError (SonarCloud S7764) Prefer globalThis over window for the reload handler, matching existing globalThis.location usage in the codebase (e.g. SideNavBar). Clears the only open SonarCloud issue on this PR (javascript:S7764, minor code smell). * UN-3185: Auto-reload once on chunk-load errors (stale chunk after deploy) The route ErrorBoundary already catches a rejected dynamic import() (a <Suspense> alone only handles the pending state). Add chunk-error handling so the common production trigger — a stale hashed chunk after a redeploy (client requests a filename the CDN no longer serves) — auto-recovers: - isChunkLoadError() detects the failed-dynamic-import error across browsers. - handleRouteError() (wired as onError on both the app-wide boundary in Router.jsx and the content-scoped one in LazyOutlet) reloads ONCE to pick up fresh chunk hashes, guarded by a sessionStorage timestamp so a genuinely-gone chunk falls through to the manual Reload fallback instead of looping. Non-chunk render errors are never auto-reloaded. This covers the outermost lazy route elements too (e.g. FullPageLayout for verticals, OnboardProduct for llm-whisperer), which render under the Router boundary.
[MISC] Reject no-op OSS releases when there are no new commits (#2117) * [MISC] Reject no-op OSS releases when there are no new commits create-release.yaml computed and published a new tag even when the branch had no commits beyond the last release, producing empty releases (e.g. v0.177.1 points at the exact same commit as v0.177.0). Add a guard after "Fetch base release version": use the compare API's ahead_by (the workflow has no local checkout) to count commits on the branch beyond the latest release tag. If zero, abort with a clear error (dry-run only warns). Works for main and hotfix branches alike. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [MISC] Harden no-op guard: validate ahead_by, fail loudly on unexpected value PR review (Greptile + internal): `[[ "$AHEAD" -eq 0 ]]` mishandled a non-integer ahead_by — an empty value evaluated true ([[ "" -eq 0 ]]) and would wrongly reject a legitimate release, and a literal "null" aborted with a cryptic "integer expression expected" / unbound-variable error. Use `--jq '.ahead_by // empty'`, fail loudly if the compare API call fails, and require ahead_by to match ^[0-9]+$ (error out otherwise) before a string compare against "0". A transient/auth/shape error now fails the step with a clear message instead of being mistaken for "no new commits". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
[MISC] Surface LLMWhisperer adapter env knobs in workers sample.env (#… …2113) [FIX] Surface LLMWhisperer adapter env knobs in workers sample.env Text extraction now runs in-process inside the file-processing workers (via the SDK x2text adapter), not in a separate tool container. The ADAPTER_LLMW_* knobs are read with os.getenv from the worker process, so they must be present in the worker env; previously they were only set on the backend/prompt envs and silently fell back to SDK defaults. Claude-Session: https://claude.ai/code/session_01SBT1USHvaBLqEi9Xd8NoBP Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[MISC] Surface LLMWhisperer adapter env knobs in workers sample.env (#… …2113) [FIX] Surface LLMWhisperer adapter env knobs in workers sample.env Text extraction now runs in-process inside the file-processing workers (via the SDK x2text adapter), not in a separate tool container. The ADAPTER_LLMW_* knobs are read with os.getenv from the worker process, so they must be present in the worker env; previously they were only set on the backend/prompt envs and silently fell back to SDK defaults. Claude-Session: https://claude.ai/code/session_01SBT1USHvaBLqEi9Xd8NoBP Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UN-3621 [HOTFIX] Structure tool no longer crashes when single-pass ex… …traction returns a non-object output (#2110) UN-3621 [FIX] Guard structure pipeline against non-dict single-pass output Single-pass extraction can return a top-level JSON array (e.g. a truncated/ runaway LLM response that hit its output-token cap). The parsed `output` is then a list, and _handle_structure_pipeline called `.values()` on it unconditionally, raising an opaque `AttributeError: 'list' object has no attribute 'values'` that failed the file with no actionable signal. Guard the output shape: if it isn't a dict, return a clear ExecutionResult failure naming the likely cause instead of crashing, and stop the malformed payload from flowing downstream as a success. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[FIX] Revert djangorestframework 3.15.2 → 3.14.0 to avoid UniqueToget… …herValidator errors (#2098) [FIX] Revert djangorestframework 3.15.2 -> 3.14.0 to unblock staging The DRF 3.15.2 bump (#2087) regressed rc.343. DRF 3.15 auto-derives multi-field UniqueTogetherValidators from model UniqueConstraints, which 3.14 only did for legacy unique_together. Two breakages followed for every ModelSerializer(fields="__all__") over a model using Meta.constraints: 1. Server-set constraint fields (e.g. organization) -> "<field>: required" on create. Partially patched by #2092 for the 5 org-attached models. 2. Client-supplied constraint fields (TableSettings, ProfileManager, agentic table settings, lookups) -> "...must make a unique set" raised at is_valid(), short-circuiting the views' intended `except IntegrityError: raise DuplicateData(<friendly>)` path. This both replaced the friendly message and moved the error from a top-level `detail` string into nested `non_field_errors`, which the frontend does not surface -> silent failures (e.g. duplicate LLM profile name, table settings no longer editable after first save). Pin back to 3.14.0 to restore the known-good behaviour across the whole unique-constraint class at once. The CVE-2024-21520 XSS patch carried by 3.15.2 is intentionally deprioritized; the 3.15 upgrade will be reattempted later with a serializer-level fix (drop auto-derived uniqueness validators). Reverts only the DRF entry from #2087; other batched bumps untouched. The org `editable=False` changes (#2092) remain correct no-ops under 3.14 (org is set server-side in save() from UserContext), so no rollback is needed there. Claude-Session: https://claude.ai/code/session_01G8hAHc4HUo42zY1g9LAjKu Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PreviousNext