Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
node_modules/
dist/
.env
.browser-auth.json
.DS_Store
tsconfig.tsbuildinfo
*.pem
Expand Down
41 changes: 27 additions & 14 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,38 @@ This document captures developer-focused guidelines and conventions, and is VERY
- `src/test/setup.ts` loads automatically via Vitest to expose browser-like globals (localStorage, fetch, atob/btoa). Keep new hook/UI tests compatible with that environment instead of redefining globals in each file.
- `src/test/mock-remote.ts` is a GitHub REST stub that powers sync-heavy tests (e.g. `useRepoData` flows). Prefer it when you need to exercise `syncBidirectional` without network calls.

### E2E / Browser Testing

When shipping a UI feature, verify the full flow end-to-end in the browser using `npx agent-browser` before calling it done. This catches wiring and UI bugs that unit tests won't catch.

Typical workflow:

1. The dev server must be running (`npm start` — the human developer usually has this up).
2. **First run requires auth**: open headed, ask the user to sign in, then save state so future runs are headless:
```bash
npx agent-browser --headed open http://localhost:3000
# ask user to sign in, then:
npx agent-browser state save .browser-auth.json
```
The file is gitignored and persists across restarts — you only need to do this once.
3. **Subsequent runs**: load saved state and test headlessly:
```bash
npx agent-browser state load .browser-auth.json
npx agent-browser open http://localhost:3000
```
4. Always clean up: `npx agent-browser close`

The dev frontend talks to the **production backend** (see `VITE_VIBENOTE_API_BASE` in `.env`), so e2e tests exercise real API flows — shares get committed to GitHub, sync hits the real server, etc.

### Commit Conventions

- Do NOT commit unless asked. Even if you were asked to commit within a session, don't commit more changes in the same session without being asked.

### Details you might not need
### Other dev notes

- Run the frontend: `npm start` (Vite on `http://localhost:3000`)
- Human developer will have this running. He can provide feedback on UI changes and test changes manually if necessary.
- (Typically not needed) Run the backend: `npm run server:start`
- We usually set `VITE_VIBENOTE_API_BASE` to the _production backend_ when developing the frontend, so we don't have to start the backend.
- Env: Human developer manually fills `.env` with GitHub App variables. Coding agents MUST NEVER read or write `.env`, actual secrets may be stored there, even in development.
- Run the frontend: `npm start` (Vite on `http://localhost:3000`). The human developer usually has this running.
- Run the backend: `npm run server:start` — typically not needed since `VITE_VIBENOTE_API_BASE` points to the production backend.
- **IMPORTANT — Env**: The human developer manually fills `.env` with GitHub App secrets. Coding agents MUST NEVER read or write `.env`, even in development.
- Node runs TypeScript directly (2025+): use `node path/to/file.ts` for quick scripts; no ts-node/tsx needed.

## Product and architecture
Expand Down Expand Up @@ -88,11 +109,3 @@ See `.env.example` and `docs/AUTH.md` for a detailed breakdown of variables and
- example: do NOT use `useMemo()` when the logic just filters an array
- React: Do not use `useCallback()` (unless you have a strong reason). Just recreating callbacks on every render is usually fast, and will ensure they are never stale.
- Prefer putting dense logic into simple top-level `function`s, instead of creating spaghetti of inline declared callbacks that implicitly depend on in-scope variables.

## NO GAMBIARRA POLICY - ASK FOR FEEDBACK INSTEAD

This is meant to be a high-quality, production codebase maintained over a long time horizon and solving difficult problems by careful engineering. In order to maintain quality, we must strive to keep the code clean, modular, simple and functional. Quick and dirty solutions must be completely avoided, in favor of robust, well-designed, general solutions.

In some case, you will be asked to perform a seemingly impossible task, either because the user is unaware, or because you don't grasp how to do it properly. In these cases, DO NOT ATTEMPT TO IMPLEMENT A HALF-BAKED SOLUTION JUST TO SATISFY THE USER'S REQUEST. If the task seems to hard, be honest that you couldn't solve it in the proper way, leave the code unchanged, explain the situation to the user and ask for further feedback and clarifications.

Similarly, sometimes a task together with other specifications will naturally lead you to a complex or ugly-looking solution. In that case, STOP AND ASK FOR FEEDBACK. It could be entirely possible that in order to complete the task cleanly, some higher-level aspect of the codebase needs to be refactored, a data type changed, or an entire new module created. Discuss this with the user instead of trying to force an ugly solution into the existing codebase.
44 changes: 36 additions & 8 deletions docs/GIT-NATIVE-SHARES.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,41 @@ Simpler for agents and humans to create — `echo "notes/foo.md" > .shares/<shar

### Task 6: Frontend — Share viewer updates

- [ ] Update share viewer to handle new URL formats
- [ ] Route `/s/<owner>/<repo>/<shareId>` → tier 1 API
- [ ] Route `/s/<segment>` → tier 2 API
- [ ] Delete legacy API which is no longer supported by the UI
- [x] Update share viewer to handle new URL formats
- [x] Route `/s/<owner>/<repo>/<shareId>` → tier 1 API
- [x] Route `/s/<segment>` → tier 2 API
- [x] Delete legacy API which is no longer supported by the UI

### Task 7: Skill doc & tooling
### Task 7: Frontend — Git-native share creation

- [ ] Update `skills/vibenote/SKILL.md` with new sharing workflow
- [ ] Helper script or instructions for generating `.shares/.repo-id` and opaque URLs
- [ ] Document how agents can create shares purely via git
Replace the server-side share creation flow with a pure-git one. Instead of `POST /v1/shares`,
the UI writes `.shares/<shareId>` directly to the repo via the GitHub Contents API and computes
the opaque URL client-side. `POST /v1/repo-id` is idempotent, so the UI always calls it when
creating a share — no need to track whether the repo was previously registered.

Share creation flow:
1. Read `.shares/.repo-id` from the repo. If absent, generate a random repoId and commit the file.
2. Call `POST /v1/repo-id` with the repoId (always — it's a no-op if already registered).
3. Generate a random shareId, compute the opaque URL segment client-side.
4. Commit `.shares/<shareId>` containing the note path.
5. Return the opaque URL to the UI immediately — no further server round-trip needed.

- [x] Implement share creation flow as above
- [x] Update `ShareDialog` to show the computed opaque URL
- [x] Revoking a share: delete `.shares/<shareId>` from the repo via GitHub Contents API
- [x] Looking up an existing share: scan local store (populated by sync, in-memory, no network call)

### Task 8: Frontend — Remove legacy sharing system

Once Task 7 is complete, the old server-side share store is fully superseded.

- [x] Remove `getShareLinkForNote`, `createShareLink`, `revokeShareLink` from `src/lib/backend.ts`
- [x] Remove old share state management from `src/data.ts`
- [x] Remove old `/v1/shares` and `/v1/share-links/:id` endpoints from `server/src/sharing.ts`
- [x] Remove `server/src/share-store.ts`
- [x] Remove `server/data/shares.json`

### Task 10: Skill doc & tooling

- [x] Update `skills/vibenote/SKILL.md` with new sharing workflow
- [x] Document how agents can create shares purely via git (covered in SKILL.md)
5 changes: 2 additions & 3 deletions server/src/__tests__/git-shares.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ describe('Tier 2 — Opaque shares', () => {
expect(json.error).toBe('share not found');
});

it('10. metadata does not leak internal details', async () => {
it('10. metadata exposes owner but not repo or shareId', async () => {
setupRepoMock(ENC_OWNER, ENC_REPO, {
[`.shares/${SHARE_ID}`]: 'notes/private.md',
'notes/private.md': MARKDOWN_BODY,
Expand All @@ -342,8 +342,7 @@ describe('Tier 2 — Opaque shares', () => {

expect(res.status).toBe(200);
const json = await res.json();
expect(json).toEqual({ ok: true });
expect(json).not.toHaveProperty('owner');
expect(json).toEqual({ owner: ENC_OWNER });
expect(json).not.toHaveProperty('repo');
expect(json).not.toHaveProperty('shareId');
});
Expand Down
46 changes: 0 additions & 46 deletions server/src/__tests__/share-store.test.ts

This file was deleted.

3 changes: 0 additions & 3 deletions server/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ type Env = {
SESSION_JWT_SECRET: string;
SESSION_STORE_FILE: string;
SESSION_ENCRYPTION_KEY: string;
SHARE_STORE_FILE: string;
REPO_ID_STORE_FILE: string;
PUBLIC_VIEWER_BASE_URL: string;
};
Expand Down Expand Up @@ -52,7 +51,6 @@ function getEnv(): Env {
const SESSION_JWT_SECRET = process.env.SESSION_JWT_SECRET ?? 'dev-session-secret-change-me';
const SESSION_STORE_FILE = process.env.SESSION_STORE_FILE ?? './server/data/sessions.json';
const SESSION_ENCRYPTION_KEY = must('SESSION_ENCRYPTION_KEY');
const SHARE_STORE_FILE = process.env.SHARE_STORE_FILE ?? './server/data/shares.json';
const REPO_ID_STORE_FILE = process.env.REPO_ID_STORE_FILE ?? './server/data/repo-ids.json';
const PUBLIC_VIEWER_BASE_URL = process.env.PUBLIC_VIEWER_BASE_URL ?? 'https://vibenote.dev';
const GITHUB_APP_ID = Number(must('GITHUB_APP_ID'));
Expand All @@ -74,7 +72,6 @@ function getEnv(): Env {
SESSION_JWT_SECRET,
SESSION_STORE_FILE,
SESSION_ENCRYPTION_KEY,
SHARE_STORE_FILE,
REPO_ID_STORE_FILE,
PUBLIC_VIEWER_BASE_URL,
};
Expand Down
3 changes: 2 additions & 1 deletion server/src/git-shares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ function gitShareEndpoints(app: express.Express) {
handleErrors(async (req, res) => {
const { owner, repo, shareId } = getOpaqueShareParams(req);
await fetchSharePath(owner, repo, shareId); // validate share exists
res.json({ ok: true });
// Return owner (repo owner name) but not repo or shareId — URL stays opaque.
res.json({ owner });
})
);

Expand Down
2 changes: 0 additions & 2 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
} from './api.ts';
import { createSessionStore } from './session-store.ts';
import { handleErrors, HttpError, requireSession } from './common.ts';
import { sharingEndpoints } from './sharing.ts';
import { gitShareEndpoints } from './git-shares.ts';

declare module 'express-serve-static-core' {
Expand Down Expand Up @@ -138,7 +137,6 @@ app.post('/v1/webhooks/github', (_req, res) => {
res.status(204).end();
});

sharingEndpoints(app);
gitShareEndpoints(app);

const server = app.listen(env.PORT, () => {
Expand Down
148 changes: 0 additions & 148 deletions server/src/share-store.ts

This file was deleted.

Loading