mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
feat(webapp): [phase 5] parity gate — docs, layout fix, migration complete
- #root { display: contents } so body's flex layout sees through the
React mount point (main pane was stuck at content width)
- README: Web frontend development section; CLAUDE.md: commands +
webapp description rewritten for the React/Vite reality (committed
dist, check-dist pre-release step, the no-router-library and
no-post-commit-DOM-patching lessons)
- verified: clean git-archive checkout builds with no Node and serves
the React app; visual parity pass across desktop + mobile surfaces;
42 e2e specs green; plugin docs untouched (no frontend internals)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
This commit is contained in:
co-authored by
Claude Fable 5
parent
f60be366e7
commit
d9a5ab3fd6
@@ -509,6 +509,26 @@ The integration tests in `internal/syncer` simulate multiple devices syncing
|
||||
through a `file://` remote, including offline operation and concurrent-edit
|
||||
conflicts. Set `BDRIVE_HOME` to relocate all beardrive state (used heavily in tests).
|
||||
|
||||
### Web frontend
|
||||
|
||||
The hub's web UI is a React + TypeScript app in `internal/webapp/frontend`
|
||||
(Vite; runtime dependencies are just react, react-dom, and
|
||||
@tanstack/react-query — routing is a small in-repo history router). Its
|
||||
**built output is committed** at `internal/webapp/static`, the `go:embed`
|
||||
target, so building or `go install`-ing the binary never needs Node.
|
||||
|
||||
Only when changing `frontend/src`:
|
||||
|
||||
```sh
|
||||
cd internal/webapp/frontend
|
||||
npm install
|
||||
npm run dev # hot-reload dev server, proxying /api to a local hub
|
||||
# (BDRIVE_DEV_PROXY=http://localhost:8993 to point elsewhere)
|
||||
npm run build # rebuild internal/webapp/static — commit the result
|
||||
npm run e2e # Playwright suite; starts its own seeded hub on :8993
|
||||
./check-dist.sh # verify the committed static/ is fresh (pre-release check)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
GNU AGPL-3.0 — Copyright 2026 Runbear, Inc. See [LICENSE](LICENSE).
|
||||
|
||||
+24
-17
@@ -200,21 +200,25 @@ refresh; invalidate after uploads/renames/admin actions — mirror today's
|
||||
open project closes the panel (the URL fallback redirect fires).
|
||||
|
||||
### Phase 5 — parity gate & swap
|
||||
- [ ] Port the 17 checks from the pre-existing smoke suite (see
|
||||
§Verification) into `frontend/e2e/` as Playwright tests; extend with:
|
||||
login→browse→open markdown file, upload roundtrip, share create/
|
||||
revoke, org admin invite flow, history view, 404 on `/api/nope`.
|
||||
- [ ] Full e2e suite green against the harness hub.
|
||||
- [ ] Old `app.js`/old `index.html`/old `style.css` gone from the repo
|
||||
(replaced by build output); `git grep renderConnectGuide` finds only
|
||||
frontend/src.
|
||||
- [ ] Docs updated: README (frontend dev section), CLAUDE.md (`webapp`
|
||||
package description — replace "dependency-free vanilla JS" with the
|
||||
React/Vite reality, build + check-dist instructions),
|
||||
`plugin/skills/beardrive/SKILL.md` only if it mentions frontend
|
||||
internals (it likely doesn't — verify).
|
||||
- [ ] `go build ./...` from a clean checkout with no Node succeeds and
|
||||
serves the React app.
|
||||
- [x] The 17 original smoke checks ported (`e2e/home.spec.ts`) and
|
||||
extended: login→browse→markdown, upload roundtrip, share
|
||||
create/revoke (+public fetch), org admin flows, history views,
|
||||
404 on `/api/nope`. 42 specs total, ~16s, stable.
|
||||
- [x] Full e2e suite green against the harness hub.
|
||||
- [x] Old `app.js`/`index.html`/`style.css` gone; `git grep
|
||||
renderConnectGuide` outside frontend/ matches only this PRD.
|
||||
- [x] Docs updated: README §Development gains a "Web frontend" section;
|
||||
CLAUDE.md Commands + `webapp` package description rewritten for the
|
||||
React/Vite reality (committed dist, check-dist, the no-router and
|
||||
no-DOM-patching lessons); plugin docs mention no frontend internals
|
||||
(verified — nothing to change).
|
||||
- [x] `go build` from a clean `git archive` checkout with no
|
||||
`frontend/` tooling succeeds and serves the React app (hashed
|
||||
assets 200, immutable).
|
||||
- [x] Visual parity pass: screenshots of home/guide, embedded insights,
|
||||
file view, folder listing, history, org admin, palette, mobile —
|
||||
after fixing `#root { display: contents }` (body's flex layout must
|
||||
see through the React mount point).
|
||||
|
||||
## Verification (every phase)
|
||||
|
||||
@@ -261,6 +265,9 @@ file path; reload on `/insights`.
|
||||
- [x] Phase 2
|
||||
- [x] Phase 3
|
||||
- [x] Phase 4
|
||||
- [ ] Phase 5
|
||||
- [x] Phase 5
|
||||
|
||||
Blockers / deviations: (record here; stop rather than deviate silently)
|
||||
Blockers / deviations: none open. Two documented deviations, both
|
||||
deliberate: react-router-dom replaced by the in-repo synchronous router
|
||||
(invariant 5), and deleting the currently-open project closes the org
|
||||
admin panel (Phase 4 note).
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { chromium } from "playwright";
|
||||
const b = await chromium.launch({ headless: true });
|
||||
const p = await b.newPage({ viewport: { width: 1360, height: 900 } });
|
||||
await p.goto("http://localhost:8993/");
|
||||
await p.waitForURL(/auth\/login/);
|
||||
await p.fill('input[name="email"]', "e2e@example.com");
|
||||
await p.fill('input[name="password"]', "e2e-pass-1");
|
||||
await p.click("form button");
|
||||
await p.waitForSelector(".guide");
|
||||
await p.waitForTimeout(800);
|
||||
const dir = "/Users/snow/.claude/jobs/31cc1f49/tmp/react-shots/";
|
||||
await p.screenshot({ path: dir + "1-home-guide.png" });
|
||||
await p.evaluate(() => document.getElementById("content").scrollTo(0, 1200));
|
||||
await p.waitForTimeout(300);
|
||||
await p.screenshot({ path: dir + "2-home-insights.png" });
|
||||
const pid = (await (await p.request.get("http://localhost:8993/api/projects")).json()).projects.find(x=>x.name==="wiki").id;
|
||||
await p.goto(`http://localhost:8993/${pid}/index.md`);
|
||||
await p.waitForSelector("#content h1");
|
||||
await p.waitForTimeout(400);
|
||||
await p.screenshot({ path: dir + "3-file-view.png" });
|
||||
await p.goto(`http://localhost:8993/${pid}/notes`);
|
||||
await p.waitForSelector(".dl-history");
|
||||
await p.waitForTimeout(400);
|
||||
await p.screenshot({ path: dir + "4-folder-listing.png" });
|
||||
await p.goto(`http://localhost:8993/${pid}/history`);
|
||||
await p.waitForSelector(".hentry");
|
||||
await p.screenshot({ path: dir + "5-history.png" });
|
||||
await p.click("#invite-btn");
|
||||
await p.waitForSelector(".admin");
|
||||
await p.waitForTimeout(400);
|
||||
await p.screenshot({ path: dir + "6-org-admin.png" });
|
||||
await p.keyboard.press("Meta+k");
|
||||
await p.waitForSelector("#palette");
|
||||
await p.fill("#palette-input", "read");
|
||||
await p.waitForTimeout(300);
|
||||
await p.screenshot({ path: dir + "7-palette.png" });
|
||||
// mobile viewport
|
||||
const m = await b.newPage({ viewport: { width: 390, height: 780 } });
|
||||
await m.goto(`http://localhost:8993/${pid}/index.md`);
|
||||
await m.waitForSelector("#content h1");
|
||||
await m.screenshot({ path: dir + "8-mobile-file.png" });
|
||||
await b.close();
|
||||
console.log("shots done");
|
||||
@@ -35,6 +35,10 @@
|
||||
* { box-sizing: border-box; }
|
||||
[hidden] { display: none !important; }
|
||||
html, body { height: 100%; margin: 0; }
|
||||
/* The React shell renders inside #root; make it transparent to layout so
|
||||
body's flex still governs sidebar + main. */
|
||||
#root { display: contents; }
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
background: var(--bg);
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,8 +5,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>BearDrive</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🐻</text></svg>">
|
||||
<script type="module" crossorigin src="/assets/index-B1CZxrv9.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-oVhdizP9.css">
|
||||
<script type="module" crossorigin src="/assets/index-BmKSe0_Y.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CemqdDtO.css">
|
||||
</head>
|
||||
<body>
|
||||
<svg width="0" height="0" class="sprite" aria-hidden="true" focusable="false">
|
||||
|
||||
Reference in New Issue
Block a user