9 Commits

Author SHA1 Message Date
Edwin He 6bb128c804 feat(web): automatic dev sharding + bare workspace URL for npm run dev (#4713)
Point OMNIGENT_URL at a bare Databricks workspace origin and npm run dev
auto-fills the /api/2.0/omnigent api-proxy mount and emits the host_id slice
key on host-scoped traffic (build-time VITE_DATABRICKS_WORKSPACE flag + the
unified isDatabricksWorkspace() gate), so the standalone dev bundle shards like
the embedded UI. An explicit mount or a local server is unaffected.

Signed-off-by: Edwin He <41037314+Edwinhe03@users.noreply.github.com>
Co-authored-by: Isaac <isaac@example.com>
2026-08-13 04:46:06 +00:00
Zeyi (Rice) Fan ef8aba3af0 refactor(web): retire the PWA service worker and update prompt (#4617)
## Related issue

N/A — `Refactor / chore`.

## Summary

The PWA landed as one squashed PR (`b6976c1b2`, #116) whose headline was
installability. #116 was authored around mid-June, when "installable Omnigent on
mobile" was an open problem; it merged 2026-06-30, by which point the iOS shell
had shipped (#965, 2026-06-22) and the Android shell landed the next day
(#1604/#1704). The native shells took over the installed-app story while the PR
was in flight, and the PWA was never re-evaluated.

What was left was load-bearing for one thing only — the "new version → Reload"
prompt — and inert for everything else:

- `web/src` had zero uses of `navigator.serviceWorker`, `caches.*`,
  `BroadcastChannel`, `pushManager`, `backgroundSync` or `setAppBadge`.
  Notifications deliberately bypass the worker
  (`web/src/lib/browserNotifications.ts`) and badges go through `nativeBridge.ts`.
- `version.json` was emitted, precached, and read by nobody.
- Installability was unadvertised (no `beforeinstallprompt`) and unmeasured (no
  `display-mode` checks), so the worker's one cache entry existed only to satisfy
  Chrome's "non-empty fetch handler" install heuristic.

Web Push (#1751, P2) is the only thing that would need a worker again, and a push
worker needs different handlers, VAPID keys and server infra — the retired file
is not useful groundwork.

ELI5: the service worker was a doorbell that only rang to say "the app has been
updated". Nothing else used it, and three native apps now do the "install
Omnigent" job it was built for, so the doorbell and its wiring come out.

A worker already registered in a browser stays registered after we stop shipping
one, so `sw.js` becomes a tombstone that removes itself:

```
deploy 0.10.0
      │
      ▼
browser fetches /sw.js (no-cache)  →  installs tombstone  →  parks in `waiting`
      │
      ├─ old tab still runs old JS, shows its own update banner one last time
      │     user clicks Reload → SKIP_WAITING → activate
      │                                          ├─ purge omnigent-pwa-* caches
      │                                          └─ registration.unregister()
      │                                                → tab reloads, PWA-free
      └─ or all tabs close → activate on next visit → same cleanup, no prompt
```

Deliberately no `skipWaiting()` on install, so nobody's agent session is
interrupted by an unprompted reload. The purge matches the retired worker's exact
cache-name shape, `/^omnigent-pwa-[0-9a-f]{8}$/` — it only ever created
`omnigent-pwa-${(hash >>> 0).toString(16).padStart(8, "0")}` — rather than
clearing Cache Storage wholesale or trusting a bare prefix, so a tombstone
lingering in some browser cannot delete a future feature's caches even if that
feature reuses the prefix.

`registration.unregister()` leaves no persistent browser state, so registering a
worker at `/sw.js` again later is clean. Two things are kept for that reason:
the `no-cache` header for `sw.js` in `app.py` (so a cached tombstone can never
shadow a future worker) and the embed-island guard that forbids shipping any
service worker into a host origin.

Tombstone deletion is targeted at **0.11.0** (marked `@deprecated` in
`web/sw-src/sw.js` and in the vite plugin).

Not in this PR: `emptyOutDir: true` deletes old hashed chunks on deploy, the app
lazy-loads most routes, and there is no `ErrorBoundary` anywhere in `web/src`, so
a tab left open across a deploy can white-screen on navigation to a lazy route.
The prompt was a proactive nudge, never a guard — it never prevented the 404. The
gap pre-dates this change (it already applied to anyone who dismissed the banner)
and the fix (ErrorBoundary + reload on failed dynamic import) is independent of
the PWA, so it is filed separately.

## Test Plan

- `pnpm --filter web run type-check`, `run lint`, `run build` — clean; build
  output contains `sw.js` only, with no `manifest.webmanifest`, no
  `version.json` and no `pwa-*.png` (`apple-touch-icon.png` / `favicon.svg`
  retained).
- `uv run pytest tests/server/integration/test_app.py::test_web_ui_serves_service_worker_uncached`
  — passes.
- `pnpm exec vitest run src/components/UpdateBanner.test.tsx` — 5 passed;
  confirms the similarly-named Electron desktop update banner is untouched.
- Exercised the rewritten build guard against the real build output, plus eight
  negative cases, to prove it is not vacuous: a worker that calls `respondWith`,
  an unscoped cache purge, a *bare-prefix* cache filter, a worker that never
  unregisters, a stale `__BUILD_VERSION__` token, a re-emitted manifest, a
  re-emitted `version.json`, and a missing `sw.js` are each rejected.
- Round-tripped the anchored cache pattern against the fingerprints the retired
  worker could produce (uint32 min, max and typical values all render as 8
  lowercase hex chars) to confirm the tightened filter still purges every legacy
  cache name, while leaving unrelated names in the same namespace alone.
- `uv run pre-commit run` — all hooks pass.

## Demo

N/A — the only visible effect is the absence of the update banner.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] UI / frontend change
- [x] Refactor / chore
- [ ] Docs
- [ ] Test / CI
- [ ] Breaking change

## Test coverage

- [ ] Unit tests added / updated
- [x] Integration tests added / updated
- [x] E2E tests added / updated
- [x] Manual verification completed
- [ ] Existing tests cover this change
- [ ] Not applicable

## Coverage notes

`tests/e2e_ui/test_pwa_e2e.py` is deleted (it asserted live PWA behaviour) and
`conftest._assert_pwa_build` is replaced by
`_assert_service_worker_tombstone`, which now enforces the *dangerous*
direction: the worker must unregister itself, must intercept nothing, must not
purge caches it does not own, and the manifest/version sentinel must be gone.
`tests/e2e_ui/test_pwa_build.py` is renamed to `test_embed_service_worker.py` and
kept — "the embed island ships no service worker" outlives the PWA.

Manual verification covered the parts a test cannot: the emitted build output was
inspected by hand, and the guard was run against both the real output and seven
mutated inputs (listed in the Test Plan) to confirm each regression is caught.
The deleted unit tests covered only the removed components.

## Changelog

Removed the "A new version of Omnigent is available" prompt and browser PWA
install support; the desktop and mobile apps remain the installable clients.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
2026-08-11 15:55:02 -07:00
Dhruv Gupta 6ac1c60454 feat(desktop): move the server picker into the sidebar, add a server version manifest (#4551)
The macOS shell hides the native title bar, and the picker filled that freed
strip with a centered "<thread> — <host>" label. But the chat header occupies
the same strip (absolute top-0, taller at h-14), so on a narrow window the
centered label ran straight into the header's action cluster.

Dock the picker at the bottom of the sidebar instead, out of the contested
space: a sidebar row (server glyph + current host + upward chevron) opening a
menu of recent servers plus "Connect to new server…". The drag strip and the
sidebar's traffic-light top margin are unchanged — those keep the OS window
controls off the sidebar card.

The picker now gates on the picker IPC resolving rather than on
isMacElectronShell(), so Windows and Linux desktop gain a picker they never
had; browsers still render nothing.

Also add GET /.well-known/omnigent.json, an unauthed version manifest for
non-browser clients. The desktop shell ships and updates on its own cadence,
so any installed build can meet any server version, and it had no way to learn
what it was talking to before loading the SPA (/v1/info is read by the SPA
after boot, too late to decide how to open a window). The shell fetches it on
every path that loads a server — startup, connect, and server switch — stores
it per window, and forwards it to the SPA.

Compat is the point of the document, in both directions:

  * Clients gate on `manifest_version >= N`, never `=== N`, so a newer server
    keeps working with an older shell. Adding a field never bumps the version.
  * A 404 (every server older than the route), an unreachable host, HTML from
    an SPA catch-all, or malformed JSON all resolve to the same pre-manifest
    baseline, which means "use existing behavior" — never an error, and never
    a blocked connection. The fetch is not awaited before loadURL.
  * `.well-known` joins the API-fallback allowlist so an unmatched path under
    it returns a JSON 404 instead of index.html. Without that, a shell probing
    an older server would get 200 text/html and could parse the SPA shell as a
    manifest — the 404 is what makes "no manifest" detectable at all.

The dev proxy forwards /.well-known too; otherwise Vite answers with
index.html and the capability is invisible in local development.

Verified end-to-end in the desktop shell run from source: server route → shell
fetch → per-window store → IPC → renderer, and the baseline fallback when the
manifest is unreachable.

Co-authored-by: Isaac

Signed-off-by: Dhruv Gupta <dhruv.gupta@databricks.com>
2026-08-10 23:06:11 +00:00
Pat Sukprasert 2ee95e1a3e chore(web): require explicit returns (#4028)
Signed-off-by: Pat Sukprasert <pat.sukprasert@databricks.com>
2026-08-04 15:42:48 +07:00
Zeyi (Rice) Fan f63b297508 perf(web): load Shiki language grammars lazily instead of in the eager core chunk (#3496)
## Related issue

N/A

## Summary

- The `manualChunks` guard that keeps Shiki in one chunk (to avoid a cyclic
  import crash — the language-index ↔ alias-map split that throws "Cannot read
  properties of undefined (reading 'flatMap')" and blanks the Monaco/file
  viewer) matched both `/shiki` and `/@shikijs/`. That also swept every
  `@shikijs/langs/<lang>` grammar — which Shiki loads via dynamic import as
  per-language chunks — into the single, eagerly `modulepreload`ed core chunk.
  So ~200 language grammars (~1.68 MB gzip) were downloaded on every initial
  page load, even though a session uses only a few languages.
- Exclude `@shikijs/langs/<lang>` from the `shiki` chunk so grammars stay lazy
  per-language chunks. Keep Shiki's core, engines, and bundle glue together so
  the cyclic core stays intra-chunk — the engines must stay too: excluding them
  re-splits the cycle across chunks and reintroduces the `flatMap` crash.
- Initial-load eager JS drops from ~11.8 MB to ~4.37 MB (Shiki 1.68 MB → 466 KB
  gzip); grammars become 427 on-demand chunks. Layout-independent (same result
  under pnpm hoisted and isolated).

## Test Plan

- `pnpm --filter web run build` succeeds.
- Verified the emitted `shiki` chunk statically imports only the rolldown
  runtime (no cross-chunk cycle) and contains `bundledLanguagesAlias`
  co-located with its reader — under both hoisted and isolated node_modules.
- Verified per-language grammar chunks (python, rust, typescript, …) are
  emitted separately and are NOT `modulepreload`ed by `index.html`.
- Recommended pre-merge smoke test: open the file viewer / Monaco editor and a
  markdown code block and confirm syntax highlighting renders.

## Demo

N/A

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] UI / frontend change
- [x] Refactor / chore
- [ ] Docs
- [ ] Test / CI
- [ ] Breaking change

## Test coverage

- [ ] Unit tests added / updated
- [ ] Integration tests added / updated
- [ ] E2E tests added / updated
- [x] Manual verification completed
- [x] Existing tests cover this change
- [ ] Not applicable

## Coverage notes

Verified via build output analysis: the `shiki` core chunk is acyclic with the
alias map co-located (cycle fix preserved), and language grammars are emitted as
separate, non-preloaded chunks. Existing Shiki/code-block tests exercise the
runtime highlighting path; this change only affects chunk grouping, not module
behavior.

## Changelog

Faster initial load — syntax-highlighting language grammars now load on demand instead of all upfront.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
2026-08-01 01:04:43 +00:00
Zeyi (Rice) Fan dc97ade9f5 chore(web): migrate web and electron to root pnpm workspace (#3328)
- Define a root pnpm-workspace.yaml with web/ and web/electron/ packages.
- Move npm overrides from web/package.json into workspace overrides, using a
  shared catalog: for react, react-dom, and shiki.
- Preserve 7-day dependency cooldown via settings.minimumReleaseAge: 10080.
- Delete web/package-lock.json and web/electron/package-lock.json; add the
  generated root pnpm-lock.yaml.
- Update web/electron/package.json scripts to use pnpm --filter web run build:overlay.
- Remove web/.npmrc and web/electron/.npmrc; no committed .npmrc (CI forces the
  public registry via env var).
- Add .github/actions/setup-pnpm so all workflows can share a pinned pnpm
  11.15.1 + Node setup.
- Convert lint.yml and web-tests.yml to pnpm; update ui-snapshot and e2e-ui
  workflows.
- Update the web-prettier pre-commit hook to run web/node_modules/.bin/prettier
  directly when present.
- Update justfile to prefer pnpm for Electron recipes and lockfile normalization.
- Ensure remaining npm-based workflows (editors/vscode/, .github/ci-deps/,
  deploy/cloudflare/) are untouched and continue to work.
- Add pdfjs-dist worker URL import so Vite emits the worker asset under pnpm's
  hoisted node_modules layout.
- Force shiki and its first-party packages into a single build chunk to avoid a
  Cyclic top-level import that produced a 'flatMap' runtime error in Monaco.
- Pin build-tool versions to the legacy npm lockfile (vite 8.1.0, tailwindcss
  4.3.1, jiti 2.7.0, lightningcss 1.32.0, postcss 8.5.15) so bundler behavior
  stays consistent with the pre-migration builds.
- Update tests/e2e_ui/test_pwa_build.py to omit the now-incorrect -- separator
  when forwarding --outDir to pnpm run build:embed.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
2026-07-27 16:22:30 -07:00
Enes Yilmaz 0e4907a812 fix(web): keep regex lookbehinds off the boot path for Safari < 16.4 (#2105)
* fix(web): keep regex lookbehinds off the boot path for Safari < 16.4

Safari older than 16.4 cannot parse regex lookbehind, and several
dependencies put one on the startup path, so iPadOS 15 rendered a blank
white page ("SyntaxError: Invalid regular expression: invalid group
specifier name"):

- mdast-util-gfm-autolink-literal (via remark-gfm) ships a lookbehind
  regex literal, which fails at parse time of the entry chunk.
- marked feature-detects lookbehind in a try/catch, but rolldown
  constant-folds the probe to `true`, hard-enabling the lookbehind path
  at module scope.
- remend (via streamdown) constructs its single-tilde repair regex at
  module scope with no guard.

Two-part fix: set build.target to the default browser baseline with the
Safari/iOS floor lowered to 15, so unsupported regex literals are
emitted as runtime RegExp() calls instead of parse-time literals, and
add a small transform that keeps marked's probe a runtime check and
gives the two unguarded constructions a never-matching fallback,
degrading email autolinking and tilde repair on those browsers instead
of crashing.

Verified against Playwright WebKit 16.0, which lacks lookbehind: the
default build reproduces the blank page, the fixed build renders the app
shell with no page errors. Modern Chromium renders identically before
and after. Bundle grows 18 KB (+0.08%).

Fixes #1978

Signed-off-by: Enes Yilmaz <115046343+EnesYilmazcode@users.noreply.github.com>

* fix(web): narrow the lookbehind transform to the affected modules

Per review: gate the rewrites to marked, remend, and mdast-util-gfm-autolink-literal by module id so every other module skips the string-replacement pass instead of running it build-wide.

Signed-off-by: Enes Yilmaz <115046343+EnesYilmazcode@users.noreply.github.com>

---------

Signed-off-by: Enes Yilmaz <115046343+EnesYilmazcode@users.noreply.github.com>
2026-07-13 09:11:19 +02:00
Bryan Li b6976c1b20 feat(ap-web): installable PWA (manifest + service worker + update prompt) (#116)
* feat(web): installable PWA (manifest + service worker + update prompt)

Rebase of PR #116 onto upstream/main (c0907f74), relocating ap-web/ -> web/
after the upstream directory rename. Squashes the four original PWA commits
(installable PWA; build/SW hardening; Playwright e2e_ui coverage; native
desktop app icons).

Conflict resolutions:
- omnigent/server/app.py: folded the `.webmanifest` MIME registration into
  upstream's new `_register_web_mimetypes()` helper (was a standalone add_type).
- tests/e2e_ui/conftest.py: kept upstream's `_codex_cli_supports_goal_mode`
  alongside `_assert_pwa_build`, and pointed `--ui-skip-build` at
  `_assert_pwa_build` (it subsumes the index.html existence check).

Verified: web build emits manifest.webmanifest + fingerprinted sw.js +
version.json + icons; oxlint shows no new findings; 14 PWA unit tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(e2e-ui): point PWA build guard at renamed web/ dir

The ap-web/ folder was renamed to web/; update the embed-build guard's
cwd so test_embed_build_ships_no_service_worker runs against the new path.

Co-authored-by: Isaac

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Daniel Lok <daniel.lok@databricks.com>
2026-07-01 14:23:40 +08:00
Daniel Lok b0348074fa refactor: rename ap-web/ to web/ and update all references (#1333) 2026-06-29 10:53:59 +08:00