Commit Graph

16 Commits

Author SHA1 Message Date
YuTengjing 0ec5e3e404 ✨ feat(agent-share): let share visitors attach files to a turn (#19569)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 23:48:24 +08:00
Arvin Xu 1eacff997e 🔒 fix(acceptance): close the holes the open discussion opened (#19448)
* 🔒 fix(acceptance): close the holes the open discussion opened

Opening an acceptance's discussion to whoever holds its public link widened
three things it should not have.

An agent belongs to its workspace, not to its readers. The author-agent gate
was scoped by the acceptance's workspace, which answers "is this agent
visible there", not "may this caller use it" — and the discussion payload
hands a visitor the ids to pick from. It is now scoped to the workspace only
for callers who actually belong to it; everyone else falls back to their own
agents.

Read-only means read-only. A `viewer` of the acceptance's own workspace was
swept in by "anyone signed in who can read it" and could write on a private
delivery, unlike the same role on topic and document comments. Commenting
now needs the public link or a non-viewer seat.

An open discussion needs a way to close an unwanted remark. Its author is
exactly who will not remove it, so the acceptance's reviewers may now take
one down — the same tombstone the author's own deletion leaves — and a
visitor gets a per-minute ceiling on how much they can land.

Two client reads of `canDelete` as "I wrote this" are replaced by the author
id, since that flag now also turns on for a moderator: whether a thread may
be closed, and whose approval the reviewer bar is showing. The read-only
line no longer flashes while the permission answer is in flight, and the
sign-in return URL moves to a shared helper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* 🔒 fix(acceptance): do not call an outside reader a departed teammate

The `former` tag infers "left the workspace" from "is not a member now".
That only holds on a delivery nobody outside could write on. With the link
public, most people in the discussion never had a seat, so the tag is
dropped there rather than guessed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* 🐛 fix(acceptance): charge the visitor ceiling where the row is written

Codex review of the open-discussion hardening, three findings.

The ceiling was counted before the write and enforced after it, so a burst
of parallel requests each read the same under-limit count and all landed —
which is the shape a flood actually arrives in. It now travels into the
write: the transaction takes an advisory lock on (acceptance, author),
inserts, counts, and rolls itself back when it is the one over the line.
Everybody else still writes in parallel.

The same move fixes a retry being punished for a remark it already made. A
replay of the tenth comment's idempotency key inserts nothing, so it is no
longer counted and answers with the row it wrote the first time, instead of
a refusal for a lost response.

`canResolveThread` reads the signed-in identity, which arrives after the
first paint, but the overlay memo did not depend on it — a thread could keep
the answer computed while nobody was signed in until an unrelated comment
changed. It is a `useCallback` on what it reads, and the memo depends on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* 🐛 fix(workbench): let the SSR electron stub answer the gateway device question

Five shared store modules read getElectronStoreState().gatewayDeviceInfo to
decide whether a working directory is local. They enter the workbench SSR graph
through the acceptance check route, where the stub had no such key, so the stub
surface guard failed the build. Give the stub the key with no device, which is
the truthful answer on the server.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 10:15:06 +08:00
Innei 2c5faed6c6 🐛 fix(theme): compose ConfigProvider outside ThemeProvider in every shell (#19457)
* 🐛 fix(theme): compose ConfigProvider outside ThemeProvider in every shell

Claude-Session: https://claude.ai/code/session_019rSFnGHUE9kq3LMCUxugzR

* ♻️ refactor(motion): always render through LazyMotion's m, never the full motion runtime

Claude-Session: https://claude.ai/code/session_019rSFnGHUE9kq3LMCUxugzR
2026-09-12 03:47:01 +08:00
Innei 48c61be524 🔥 chore(theme): remove AntdStaticMethods now that nothing renders through antd static APIs (#19458)
Claude-Session: https://claude.ai/code/session_019rSFnGHUE9kq3LMCUxugzR
2026-09-12 02:40:29 +08:00
Innei db663d7077 ⚡️ perf(spa): keep first-screen and lazy modules in separate chunks (#19288)
Every manual rolldown group gets a $initial-tagged variant so first-screen
modules never share a chunk with lazy ones, an app-initial catch-all folds
the rest of the entry's static graph into one chunk, and dependency capture
stops being recursive so lazy groups cannot drag first-screen modules into a
chunk the entry then has to preload.

Web production build: 71 eager chunks / 2299 KB gzip -> 10 / 2162 KB.

Claude-Session: https://claude.ai/code/session_01QKr8CsFLYmrgqbRTSipTNH
2026-09-09 00:32:32 +08:00
Arvin Xu 9a71b5e0d4 ♻️ refactor: organize acceptance viewer and improve mobile flow review (#19290) 2026-09-08 23:09:04 +08:00
YuTengjing 8ea955ddc5 ✨ feat(model-runtime): add Meta AI provider with Muse Spark 1.3 (#19152)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 14:15:05 +08:00
Innei 011f947cf7 👷 ci(share,workbench): upload PR previews to sibling -preview Workers (#19103)
* 👷 ci(share,workbench): upload PR previews to sibling -preview Workers

Cloudflare rolls back only to the 100 most recently uploaded versions of a
script, and PR preview uploads landed on the production scripts, so a busy
day pushed every real deployment out of the rollback window. Previews now
go to lobehub-share-preview and lobehub-workbench-preview; the preview
aliases move with them (pr<N>-lobehub-share-preview...).

Claude-Session: https://claude.ai/code/session_01BKMAksDxdePWPW4kgr9KpM

* 📝 docs(split-micro-app): previews upload to a sibling -preview Worker

Claude-Session: https://claude.ai/code/session_01BKMAksDxdePWPW4kgr9KpM

* 👷 ci(share,workbench): stamp actor, commit and run on every Worker version

Cloudflare records only the API token's owner as a version's author, so a
deploy from CI or a laptop was unattributable. wrangler deploy and PR preview
uploads now carry --tag <sha7> and --message "<actor> <repo>@<sha7> run <id>"
(local deploys: "<git email> local@<sha7>"), which 鳥居番's Deploys page
already shows per version.

Claude-Session: https://claude.ai/code/session_01BKMAksDxdePWPW4kgr9KpM

* 📝 docs(split-micro-app): 鳥居番 Deploys page has Releases and Previews tabs

Claude-Session: https://claude.ai/code/session_01BKMAksDxdePWPW4kgr9KpM

* 📝 docs(split-micro-app): releases and previews live on the target's Worker card

Claude-Session: https://claude.ai/code/session_01BKMAksDxdePWPW4kgr9KpM

* 📝 docs(split-micro-app): Set override writes the staging config directly

Claude-Session: https://claude.ai/code/session_01BKMAksDxdePWPW4kgr9KpM
2026-09-04 02:53:34 +08:00
Innei f32a4eb9c5 ♻️ refactor: migrate ActionIcon, Avatar, Tag and Text to @lobehub/ui/base-ui (#18729)
The antd-based wrappers are deprecated since @lobehub/ui@5.33.0. This moves every call site to the base-ui subpath.

Beyond the import path, the migration required:

- ActionIcon now renders a native <button> instead of a div: dropped the hardcoded 'nativeButton: false' in ActionDropdown's trigger props (it overrode the library's own inference, so Base UI applied role/aria-disabled to a real button), and retyped the dictation control refs to HTMLButtonElement
- Avatar is no longer antd's: replaced src/icon with avatar, dropped gap (no equivalent)
- Tag is no longer antd's: dropped bordered={false}, which antd v6 already ignored whenever variant was set — and the wrapper always set it
- prop types (ActionIconProps, AvatarProps) now come from the base-ui subpath, since the legacy ones carry antd props and a div ref
- seven files that still imported Button from @lobehub/ui or antd are migrated too, since touching them surfaced the existing no-restricted-imports error: variant=filled -> type=fill, variant=outlined + color=default -> type=default

Verified: type-check clean, 503 tests pass across ChatInput/NavPanel/User, and the migrated surfaces render with no Base UI warnings.
2026-08-27 15:43:45 +08:00
Arvin Xu 7c559cbd4d ♻️ refactor(acceptance): move features/Verify to features/Acceptance (#18703)
The product surface is the Acceptance domain: every route is /acceptance, and what lived under features/Verify was either the acceptance page itself or one of its sub-objects — a round's report, the in-chat round card, the criteria editor. 'Verify' no longer named anything of its own, and the page sat at features/Verify/Acceptance, one level deeper than its siblings.

New layout, grouped by sub-object instead of nesting Acceptance/Acceptance:
- Viewer/        the acceptance page atoms (was Verify/Acceptance)
- Workspace/     the acceptance list panel (was Verify/Acceptance/Workspace) + useReportPanelExpand
- Report/        ReportViewer and the evidence/visualization renderers (was Verify/components)
- Run/           RunResult and CheckerDock, the in-chat round card
- CriterionList/, hooks, utils, routeMeta unchanged at the root

Removes the dead VerifyWorkspace / ReportListPanel / EmptyDetail trio (zero consumers since AcceptanceWorkspace replaced it). Pure move otherwise: git detects 90 renames; imports rewritten across src, apps/workbench and packages, plus the path literals in the workbench stub tests and two skill docs.

Co-authored-by: Arvin Xu <arvin.x@lobehub.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 10:52:03 +08:00
Innei b495d15654 💄 style(workbench): align mobile padding baseline on acceptance detail (#18616) 2026-08-23 14:34:17 +08:00
Innei 2df9829a5e ✨ feat(share): extract share pages into a standalone SSR micro app (#18496)
* ✨ feat(share): extract share pages into a standalone SSR micro app

`/share/t/:id` and `/share/page/:id` were client-only routes inside the main
SPA, so a shared link opened on a blank shell and link previews carried the
generic app card. They now live in `apps/share`, built the same way as
`apps/workbench`: a React Router framework-mode app that server-renders on a
Cloudflare Worker for cloud, plus a plain SPA build that Next serves for
self-hosted deployments.

What the server renders is the shell, the hero and a complete meta set — title,
description and the OG/Twitter card — resolved from the route loader through a
per-request tRPC client. The conversation and page-viewer stacks stay on the
client: `clientOnly()` gates them and, in the SSR environment, a resolver stub
cuts every `*.client` module out of the worker graph entirely. Gating the render
alone does not help, because the dynamic import still pulls the graph into the
bundle.

Keeping that worker deployable took three more cuts, each found by tracing the
module graph rather than guessing:

- `@/store/user` is stubbed for SSR — `GroupAvatar` reads the viewer profile,
  and the store drags the model catalog and every provider behind it (39MB→19MB)
- `shiki` and `shiki/wasm` are stubbed for SSR — the `@lobehub/ui` barrel reads
  the bundled-language catalog at module scope, registering ~400 grammars for a
  highlighter the server never runs (19MB→7.6MB, 1.78MB gzip)
- SSR i18n resolves only the namespaces the shell renders; the generic loader
  would register every locale x namespace pair as a worker chunk

The conversation list keeps its `headerSlot` while it shows the skeleton. It
returned early before, so mounting the list to fetch blanked a title the server
had already rendered.

`/share` is removed from the desktop and mobile routers and rewritten by the
Next middleware to `/spa-share/<locale>/…`, mirroring how `/verify` reaches
workbench. The cloud worker is published from lobehub-cloud, which builds this
same app against its business overlay, so this repo ships no deploy workflow
for it.

* 🐛 fix(share): preload the error namespace so the boundary renders translated

The root ErrorBoundary reaches for the `error` namespace, which SSR did not
bundle, so a failed share page rendered `error.title error.desc error.retry`
verbatim. Fetching it on demand does not fix that case: a chunk-load failure is
exactly when the client can never load the dictionary to correct itself.

* 🐛 fix(share): drop the dev template fetch that served the main SPA

The handler asked the main Vite server for `/index.share.html`, which does not
exist at the repo root. Vite answers that with the HTML fallback — 200, main SPA
shell — so `/share/*` in `bun run dev` silently booted an app that no longer has
those routes. Share is developed against its own Vite server, so the branch goes
rather than the file being added.

`spaDevShells.test.ts` guards both directions: every remaining dev template must
exist at the root and point at its own app entry, and share must not reintroduce
one.

* 📝 docs(skill): record the cloud-overlay split in split-micro-app

Share is the second micro app and the first whose rendering code lives in the
lobehub-cloud business overlay, so the skill needed the ownership rules that
decides which repo builds and deploys, and the Vite 8 tsconfig-nearest-importer
landmine that makes an overlay build ship the open-source fallback with a green
build.

Also folds in what the extraction turned up: the full Docker chain, the dev shell
that answers 200 with the wrong app, shiki and @lobehub/ui bundle cuts, the error
namespace, hydration and virtualized-prefill rules, the asset prefix convention,
and the prod promote gate.
2026-08-20 17:53:41 +08:00
Innei 3d751686af ✨ feat(acceptance): restore in-app workspace and assemble the public page from atoms (#18501)
* ✨ feat(acceptance): restore in-app workspace and assemble the public page from atoms

Keep the full owner workspace in the main SPA and compose the workbench share page from read-only capability atoms so owner actions stay out of the public bundle.

* 📝 docs(compose-atoms): generalize the skill around grain and sink-state

Make the kit apply to any heavy domain feature, and keep focus review filling the in-app frame.

* ⚡ perf(workbench): slim the public page and stop shipping shiki in the bundle

Keep the share surface to identity only, shim the browser lambda client to cookies, stub shiki on SSR, and load grammars from esm.sh on the client.

* ✨ feat(acceptance): show goal and check inventory on workbench detail

* 🐛 fix(acceptance): handle null acceptanceId in useAcceptanceBundle

Signed-off-by: Innei <tukon479@gmail.com>

* ✨ feat(workbench): put a go-to-app CTA on the public header

* ✅ test(workbench): fail the build when the graph uses stub-missing APIs

Keep workbench stubs as an explicit surface and scan the module graph on
build:rr so a later call to a simplified-away method is a build failure.

* 🐛 fix(acceptance): type verdict helpers with TFunction

Database CI lint failed because useTranslation('verify') is not assignable
to the hand-written (key: string) => string helper signatures.

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-08-20 16:45:21 +08:00
Arvin Xu ff9aa0ebf6 ✨ feat: group acceptance deliveries by project (#18448)
* ✨ feat(acceptance): group deliveries by project

* ⚡️ perf(acceptance): persist project association

* ✨ feat(project): associate durable works
2026-08-20 13:00:15 +08:00
Innei 1477aa2ca7 💚 ci(workbench): upload CDN assets to R2 before deploying the worker (#18492)
CI was building the worker and wrangler-deploying without `bun run deploy`,
so hashed `/workbench/` assets never reached R2. Run the full deploy script
and inject the shared `ASSET_S3_*` web-assets credentials.
2026-08-19 18:44:11 +08:00
Innei 1001a58a22 ✨ feat(workbench): React Router framework mode SSR on Cloudflare Workers (#18473)
* ♻️ refactor(spa): extract verify/acceptance into apps/workbench

Move the Workbench SPA into apps/workbench and serve /verify and
/acceptance from that runtime for every web UA. The main SPA no
longer registers those page routes, so they drop out of its bundle.

* 🔧 chore(docker): wire the workbench workspace into image builds

Copy apps/workbench/package.json before pnpm i so Docker can resolve
the extracted SPA. Keep generateSpaTemplates optional for Cloud, and
require the HTML only when WORKBENCH_REQUIRED=1 (build:docker).

* ♻️ refactor(workbench): hard-navigate only from markdown entity links

Keep Link and useWorkspaceAwareNavigate in-router. Markdown verify,
acceptance, and mobile agent-doc links document-navigate. Drop the
__WORKBENCH__ Vite define; Electron still skips hard-nav for portals.

* ♻️ refactor(verify): slot-inject origin conversation and decouple shared components

* 🐛 fix(i18n): make vite glob loaders independent of the vite root

* ✨ feat(workbench): React Router framework mode SSR on Cloudflare Workers

* 🐛 fix(verify): render viewer-timezone timestamps only after hydration

* 💚 ci(workbench): deploy with affected detection and PR build verification

* 📝 docs(skills): micro-app split and gateway routing playbook

* 🐛 fix(workbench): track app/lib swallowed by the root lib/ ignore rules

* ✨ feat(workbench): signed-in acceptance list sidebar on detail pages

* 🐛 fix(workbench): survive shared-origin gateway quirks in client navigation

* 💄 style(workbench): surface error detail on the boundary page

* 💄 style(workbench): adopt the shared ErrorCapture page for the root boundary

* 💄 style(verify): drop the app-home back arrow from the acceptance list panel

* 🔧 chore(workbench): persist only error logs in wrangler observability

* 📝 docs(skills): record Docker vs Cloud hosting for micro apps

OSS Docker still builds and serves the SPA in-image. Cloud Next must
not build, rewrite, or CDN-upload it — gateway routes to the Worker.

* 💄 style(workbench): pin the acceptance logo while the list panel slides

* 🐛 fix(workbench): only show the acceptance sidebar when signed in

* 🐛 fix(verify): drop namespace prefix on bound verify translations
2026-08-19 18:21:00 +08:00