From a60b0fb9bd218f0f6ea1dccbb828d5012c9c28a6 Mon Sep 17 00:00:00 2001 From: Montasim Date: Wed, 5 Aug 2026 09:30:25 +0600 Subject: [PATCH] feat: add VidQuery product website --- .github/workflows/release.yml | 9 +- .gitignore | 7 + .prettierignore | 6 + DEPLOYMENT.md | 15 +- README.md | 57 +- apps/extension/.prettierignore | 2 + .../extension/components.json | 0 .../extension/entrypoints}/background.ts | 0 .../extension/entrypoints}/popup/index.html | 0 .../extension/entrypoints}/popup/main.tsx | 0 .../entrypoints}/sidepanel/index.html | 0 .../extension/entrypoints}/sidepanel/main.tsx | 0 .../extension/entrypoints}/youtube.content.ts | 0 .../extension/eslint.config.mjs | 0 {icons => apps/extension/icons}/128.png | Bin {icons => apps/extension/icons}/16.png | Bin {icons => apps/extension/icons}/32.png | Bin {icons => apps/extension/icons}/48.png | Bin apps/extension/package.json | 58 + .../extension/src}/application/errors.ts | 0 {src => apps/extension/src}/domain/schemas.ts | 0 .../extension/src}/infrastructure/gemini.ts | 0 .../infrastructure/storage/chrome-storage.ts | 0 .../storage/credential-vault.ts | 0 .../src}/infrastructure/youtube-context.ts | 0 {src => apps/extension/src}/shared/cn.ts | 0 .../extension/src}/shared/protocol.ts | 0 .../src}/ui/components/brand-mark.tsx | 0 .../extension/src}/ui/components/brand.tsx | 0 .../extension/src}/ui/components/button.tsx | 0 .../src}/ui/components/context-signal.tsx | 0 .../src}/ui/components/support-link.tsx | 0 .../extension/src}/ui/content/launcher.ts | 0 {src => apps/extension/src}/ui/globals.css | 0 .../extension/src}/ui/popup/popup-app.tsx | 0 .../src}/ui/sidepanel/sidepanel-app.tsx | 0 .../extension/tests}/helpers/chrome.ts | 0 {tests => apps/extension/tests}/setup.ts | 0 .../tests}/unit/credential-vault.test.ts | 0 .../extension/tests}/unit/gemini.test.ts | 0 .../extension/tests}/unit/launcher.test.ts | 0 .../extension/tests}/unit/popup-app.test.tsx | 0 .../tests}/unit/sidepanel-app.test.tsx | 0 .../extension/tests}/unit/storage.test.ts | 0 .../tests}/unit/youtube-context.test.ts | 0 tsconfig.json => apps/extension/tsconfig.json | 0 .../extension/vitest.config.ts | 0 wxt.config.ts => apps/extension/wxt.config.ts | 0 apps/web/.gitignore | 16 + apps/web/.prettierignore | 4 + apps/web/README.md | 28 + apps/web/components.json | 25 + apps/web/eslint.config.js | 24 + apps/web/netlify.toml | 12 + apps/web/package.json | 53 + apps/web/prettier.config.js | 10 + apps/web/public/brand/logo.svg | 7 + apps/web/public/prototypes/v1/index.html | 1360 ++ apps/web/src/components/brand-mark.tsx | 16 + apps/web/src/components/context-wave.tsx | 27 + apps/web/src/components/product-demo.tsx | 66 + apps/web/src/components/site-header.tsx | 109 + apps/web/src/components/ui/accordion.tsx | 85 + apps/web/src/components/ui/button.tsx | 68 + apps/web/src/components/ui/sheet.tsx | 143 + apps/web/src/lib/utils.ts | 7 + apps/web/src/routeTree.gen.ts | 68 + apps/web/src/router.tsx | 19 + apps/web/src/routes/__root.tsx | 52 + apps/web/src/routes/index.tsx | 441 + apps/web/src/styles.css | 316 + apps/web/tsconfig.json | 35 + apps/web/tsr.config.json | 3 + apps/web/vite.config.ts | 21 + package.json | 60 +- pnpm-lock.yaml | 16508 +++++++++++++--- pnpm-workspace.yaml | 9 + prototypes/landing/README.md | 17 + prototypes/landing/index.html | 1006 + prototypes/v1/index.html | 14 + 80 files changed, 17686 insertions(+), 3097 deletions(-) create mode 100644 apps/extension/.prettierignore rename components.json => apps/extension/components.json (100%) rename {entrypoints => apps/extension/entrypoints}/background.ts (100%) rename {entrypoints => apps/extension/entrypoints}/popup/index.html (100%) rename {entrypoints => apps/extension/entrypoints}/popup/main.tsx (100%) rename {entrypoints => apps/extension/entrypoints}/sidepanel/index.html (100%) rename {entrypoints => apps/extension/entrypoints}/sidepanel/main.tsx (100%) rename {entrypoints => apps/extension/entrypoints}/youtube.content.ts (100%) rename eslint.config.mjs => apps/extension/eslint.config.mjs (100%) rename {icons => apps/extension/icons}/128.png (100%) rename {icons => apps/extension/icons}/16.png (100%) rename {icons => apps/extension/icons}/32.png (100%) rename {icons => apps/extension/icons}/48.png (100%) create mode 100644 apps/extension/package.json rename {src => apps/extension/src}/application/errors.ts (100%) rename {src => apps/extension/src}/domain/schemas.ts (100%) rename {src => apps/extension/src}/infrastructure/gemini.ts (100%) rename {src => apps/extension/src}/infrastructure/storage/chrome-storage.ts (100%) rename {src => apps/extension/src}/infrastructure/storage/credential-vault.ts (100%) rename {src => apps/extension/src}/infrastructure/youtube-context.ts (100%) rename {src => apps/extension/src}/shared/cn.ts (100%) rename {src => apps/extension/src}/shared/protocol.ts (100%) rename {src => apps/extension/src}/ui/components/brand-mark.tsx (100%) rename {src => apps/extension/src}/ui/components/brand.tsx (100%) rename {src => apps/extension/src}/ui/components/button.tsx (100%) rename {src => apps/extension/src}/ui/components/context-signal.tsx (100%) rename {src => apps/extension/src}/ui/components/support-link.tsx (100%) rename {src => apps/extension/src}/ui/content/launcher.ts (100%) rename {src => apps/extension/src}/ui/globals.css (100%) rename {src => apps/extension/src}/ui/popup/popup-app.tsx (100%) rename {src => apps/extension/src}/ui/sidepanel/sidepanel-app.tsx (100%) rename {tests => apps/extension/tests}/helpers/chrome.ts (100%) rename {tests => apps/extension/tests}/setup.ts (100%) rename {tests => apps/extension/tests}/unit/credential-vault.test.ts (100%) rename {tests => apps/extension/tests}/unit/gemini.test.ts (100%) rename {tests => apps/extension/tests}/unit/launcher.test.ts (100%) rename {tests => apps/extension/tests}/unit/popup-app.test.tsx (100%) rename {tests => apps/extension/tests}/unit/sidepanel-app.test.tsx (100%) rename {tests => apps/extension/tests}/unit/storage.test.ts (100%) rename {tests => apps/extension/tests}/unit/youtube-context.test.ts (100%) rename tsconfig.json => apps/extension/tsconfig.json (100%) rename vitest.config.ts => apps/extension/vitest.config.ts (100%) rename wxt.config.ts => apps/extension/wxt.config.ts (100%) create mode 100644 apps/web/.gitignore create mode 100644 apps/web/.prettierignore create mode 100644 apps/web/README.md create mode 100644 apps/web/components.json create mode 100644 apps/web/eslint.config.js create mode 100644 apps/web/netlify.toml create mode 100644 apps/web/package.json create mode 100644 apps/web/prettier.config.js create mode 100644 apps/web/public/brand/logo.svg create mode 100644 apps/web/public/prototypes/v1/index.html create mode 100644 apps/web/src/components/brand-mark.tsx create mode 100644 apps/web/src/components/context-wave.tsx create mode 100644 apps/web/src/components/product-demo.tsx create mode 100644 apps/web/src/components/site-header.tsx create mode 100644 apps/web/src/components/ui/accordion.tsx create mode 100644 apps/web/src/components/ui/button.tsx create mode 100644 apps/web/src/components/ui/sheet.tsx create mode 100644 apps/web/src/lib/utils.ts create mode 100644 apps/web/src/routeTree.gen.ts create mode 100644 apps/web/src/router.tsx create mode 100644 apps/web/src/routes/__root.tsx create mode 100644 apps/web/src/routes/index.tsx create mode 100644 apps/web/src/styles.css create mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/tsr.config.json create mode 100644 apps/web/vite.config.ts create mode 100644 pnpm-workspace.yaml create mode 100644 prototypes/landing/README.md create mode 100644 prototypes/landing/index.html diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a38f26a..9827621 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,7 @@ jobs: runs-on: ubuntu-latest env: RELEASE_ZIP: VidQuery-${{ github.ref_name }}-chrome-unpacked.zip + LATEST_ZIP: VidQuery-chrome-unpacked.zip steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -24,19 +25,20 @@ jobs: - run: pnpm install --frozen-lockfile - name: Validate tag and extension version run: | - package_version="$(node -p "require('./package.json').version")" + package_version="$(node -p "require('./apps/extension/package.json').version")" test "${GITHUB_REF_NAME}" = "v${package_version}" - run: pnpm release:zip - name: Prepare unpacked Chrome archive run: | mkdir -p release - source_zip="$(find .output -maxdepth 1 -type f -name '*-chrome.zip' -print -quit)" + source_zip="$(find apps/extension/.output -maxdepth 1 -type f -name '*-chrome.zip' -print -quit)" test -n "${source_zip}" cp "${source_zip}" "release/${RELEASE_ZIP}" + cp "${source_zip}" "release/${LATEST_ZIP}" - name: Validate unpacked package layout run: unzip -Z1 "release/${RELEASE_ZIP}" | grep -Fxq manifest.json - name: Create checksums - run: sha256sum "${RELEASE_ZIP}" > SHA256SUMS.txt + run: sha256sum "${RELEASE_ZIP}" "${LATEST_ZIP}" > SHA256SUMS.txt working-directory: release - uses: softprops/action-gh-release@v2 with: @@ -45,4 +47,5 @@ jobs: fail_on_unmatched_files: true files: | release/${{ env.RELEASE_ZIP }} + release/${{ env.LATEST_ZIP }} release/SHA256SUMS.txt diff --git a/.gitignore b/.gitignore index 71d5d8b..1722b59 100644 --- a/.gitignore +++ b/.gitignore @@ -208,3 +208,10 @@ yarn-lock.json # Tarball archives generated by packaging tools (e.g., npm pack); ignore all .tgz files. *.tgz + +# Workspace build and dependency output. +**/node_modules/ +**/dist/ +**/.output/ +**/.wxt/ +**/.netlify/ diff --git a/.prettierignore b/.prettierignore index 032055d..e7b0d5a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -75,6 +75,12 @@ .out/ .output/ .wxt/ +**/.output/ +**/.wxt/ +**/.netlify/ +**/dist/ +apps/web/public/prototypes/ +apps/web/src/routeTree.gen.ts #################################################### diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 13a9be8..ade2d32 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -9,14 +9,14 @@ pnpm install --frozen-lockfile pnpm check ``` -The Chrome build is written to `.output/chrome-mv3/`. +The Chrome build is written to `apps/extension/.output/chrome-mv3/`. To test it locally: 1. Open `chrome://extensions`. 2. Enable **Developer mode**. 3. Select **Load unpacked**. -4. Choose `.output/chrome-mv3/`. +4. Choose `apps/extension/.output/chrome-mv3/`. 5. Open the popup, connect Gemini, then test the Side Panel on watch, Shorts, and live-video routes. ## Create the distributable archive @@ -25,13 +25,13 @@ To test it locally: pnpm release:zip ``` -`release:zip` runs the complete verification suite before WXT creates a ZIP under `.output/`. Inspect the generated manifest and archive contents before distributing it. +`release:zip` runs the complete verification suite before WXT creates a ZIP under `apps/extension/.output/`. Inspect the generated manifest and archive contents before distributing it. ## Publish a GitHub Release The tag-triggered [Release workflow](.github/workflows/release.yml) publishes the same unpacked Chrome archive pattern used by the reference extensions. -1. Update the version in `package.json` and ensure the lockfile is current. +1. Update the version in `apps/extension/package.json` and ensure the root lockfile is current. 2. Update [.github/RELEASE_NOTES.md](.github/RELEASE_NOTES.md) for that version. 3. Run `pnpm check` locally. 4. Commit and merge the release-ready changes. @@ -42,9 +42,10 @@ The tag-triggered [Release workflow](.github/workflows/release.yml) publishes th git push origin v2.0.0 ``` -The workflow rejects a tag that does not equal `v` plus the version in `package.json`. A successful run publishes: +The workflow rejects a tag that does not equal `v` plus the version in `apps/extension/package.json`. A successful run publishes: - `VidQuery-vX.Y.Z-chrome-unpacked.zip`; +- `VidQuery-chrome-unpacked.zip`, the stable landing-page download target; - `SHA256SUMS.txt`; and - the curated release notes from `.github/RELEASE_NOTES.md`. @@ -52,9 +53,9 @@ The archive is validated before publication to ensure `manifest.json` is at its ## Release checklist -- Confirm `package.json` contains the intended extension version. +- Confirm `apps/extension/package.json` contains the intended extension version. - Run `pnpm check` from a clean checkout with the committed pnpm lockfile. -- Load `.output/chrome-mv3/` into the minimum supported Chrome version. +- Load `apps/extension/.output/chrome-mv3/` into the minimum supported Chrome version. - Verify first-time consent, validated and unvalidated key saves, key removal, and legacy-key migration. - Verify watch, Shorts, live, unsupported-page, transcript-available, and transcript-unavailable states. - Verify question submission, Markdown rendering, edit, retry, quota, invalid-key, and offline errors. diff --git a/README.md b/README.md index a827aaf..b32f18b 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ > Ask Gemini questions grounded in the YouTube video you are watching, from a focused Chrome Side Panel. -[![CI](https://github.com/montasim/youtube-helper/actions/workflows/ci.yml/badge.svg)](https://github.com/montasim/youtube-helper/actions/workflows/ci.yml) +[![CI](https://github.com/montasim/VidQuery/actions/workflows/ci.yml/badge.svg)](https://github.com/montasim/VidQuery/actions/workflows/ci.yml) [![Support on SupportKori](https://img.shields.io/badge/Support_on-SupportKori-00B8B5)](https://www.supportkori.com/montasim) VidQuery is a Chromium extension for people who want to question a video without moving its transcript and metadata into a separate chat tool. Open a YouTube watch, Shorts, or live-video page; the extension collects a bounded Video Context locally and sends it directly to Google Gemini only after you submit a question. The V2 extension uses a Chrome Side Panel for conversations and a compact toolbar popup for consent and Gemini connection setup. It has no intermediary backend, account, analytics service, or persistent conversation archive. -**[Browse releases](https://github.com/montasim/youtube-helper/releases) · [Preview the approved interface](prototypes/v1/index.html) · [Report an issue](https://github.com/montasim/youtube-helper/issues)** +**[Browse releases](https://github.com/montasim/VidQuery/releases) · [Preview the approved interface](prototypes/v1/index.html) · [Report an issue](https://github.com/montasim/VidQuery/issues)** ## What it does @@ -28,7 +28,7 @@ The V2 extension uses a Chrome Side Panel for conversations and a compact toolba Once a release has been published by the tag workflow: -1. Open [GitHub Releases](https://github.com/montasim/youtube-helper/releases). +1. Open [GitHub Releases](https://github.com/montasim/VidQuery/releases). 2. Download `VidQuery-vX.Y.Z-chrome-unpacked.zip` and `SHA256SUMS.txt`. 3. Place both files in the same folder and verify the download: @@ -54,19 +54,19 @@ GitHub-installed builds do not update automatically. Repeat this process for eac ### Build and load ```bash -git clone https://github.com/montasim/youtube-helper.git -cd youtube-helper +git clone https://github.com/montasim/VidQuery.git +cd VidQuery pnpm install -pnpm build +pnpm build:extension ``` 1. Open `chrome://extensions`. 2. Enable **Developer mode**. 3. Select **Load unpacked**. -4. Choose `.output/chrome-mv3/` from this repository. +4. Choose `apps/extension/.output/chrome-mv3/` from this repository. 5. Pin VidQuery to the browser toolbar if desired. -For an installable archive, run `pnpm zip`; WXT writes the packaged extension under `.output/`. +For an installable archive, run `pnpm release:zip`; WXT writes the packaged extension under `apps/extension/.output/`. ## First use @@ -121,6 +121,7 @@ flowchart LR | Session credential cache | Restricted `chrome.storage.session` | | Device encryption key | Non-exportable Web Crypto key in IndexedDB | | Tests | Vitest, Testing Library, Happy DOM, fake-indexeddb | +| Product website | TanStack Start, shadcn, Tailwind CSS 4, Netlify | The project’s domain language is in [CONTEXT.md](CONTEXT.md). Architectural trade-offs are recorded under [docs/adr](docs/adr). @@ -128,34 +129,30 @@ The project’s domain language is in [CONTEXT.md](CONTEXT.md). Architectural tr ```bash pnpm install -pnpm dev +pnpm dev:extension ``` WXT prints the development output path. Load that unpacked directory in Chrome, keep a YouTube video open, and reload the extension after permission or manifest changes. -| Command | Purpose | -| ------------------- | ------------------------------------------------------ | -| `pnpm dev` | Start WXT development mode | -| `pnpm build` | Build the Chrome Manifest V3 extension | -| `pnpm zip` | Build and package the extension | -| `pnpm release:zip` | Run the full quality gate and package the extension | -| `pnpm typecheck` | Check TypeScript without emitting files | -| `pnpm lint` | Run ESLint with zero warnings allowed | -| `pnpm format:check` | Verify Prettier formatting | -| `pnpm test` | Run the Vitest suite once | -| `pnpm check` | Run formatting, linting, type checks, tests, and build | +| Command | Purpose | +| ---------------------- | ------------------------------------------------------- | +| `pnpm dev:extension` | Start WXT extension development | +| `pnpm dev:web` | Start the TanStack Start landing page | +| `pnpm build:extension` | Build the Chrome Manifest V3 extension | +| `pnpm build:web` | Build the Netlify-ready website | +| `pnpm release:zip` | Validate and package the extension | +| `pnpm check:extension` | Run extension formatting, lint, types, tests, and build | +| `pnpm check:web` | Run website formatting, lint, types, and Netlify build | +| `pnpm check` | Run the complete workspace quality gate | ## Project structure ```text -entrypoints/ WXT background, content-script, popup, and Side Panel entry points -src/application/ Application errors and orchestration boundaries -src/domain/ Validated Video Context and retained-data schemas -src/infrastructure/ Gemini adapter and browser storage implementations -src/shared/ Typed browser protocol and shared utilities -src/ui/ Tailwind/shadcn components and React surfaces -tests/ Credential, storage, prompt, and UI verification -prototypes/v1/ Standalone approved interface reference +apps/extension/ WXT extension source, tests, and package configuration +apps/web/ TanStack Start, shadcn, Tailwind, and Netlify website +assets/brand/ Canonical VidQuery brand assets +prototypes/v1/ Standalone approved extension interface reference +prototypes/landing/ Original static landing-page reference docs/adr/ Accepted architecture decisions ``` @@ -171,13 +168,13 @@ docs/adr/ Accepted architecture decisions ## Automated releases -Pushing a version tag such as `v2.0.0` runs the [Release workflow](.github/workflows/release.yml). The tag must match `package.json`. The workflow installs the locked dependency graph, runs the complete quality gate, builds the WXT archive, verifies that `manifest.json` is at its root, generates a SHA-256 checksum, and creates a GitHub Release containing both files. +Pushing a version tag such as `v2.0.0` runs the [Release workflow](.github/workflows/release.yml). The tag must match `apps/extension/package.json`. The workflow installs the locked dependency graph, runs the complete quality gate, builds the WXT archive, verifies that `manifest.json` is at its root, generates SHA-256 checksums, and creates a GitHub Release with versioned and stable download filenames. Release descriptions come from [.github/RELEASE_NOTES.md](.github/RELEASE_NOTES.md) and should be updated before tagging. See [DEPLOYMENT.md](DEPLOYMENT.md) for the maintainer checklist and exact release procedure. The repository does not publish this extension to npm or submit it automatically to the Chrome Web Store. ## Support and security -Use [GitHub Issues](https://github.com/montasim/youtube-helper/issues) for reproducible bugs and feature requests. Include the browser version, YouTube route type, whether a transcript was available, and the visible error message. Never include an API key, transcript, private question, or full Gemini request in an issue. +Use [GitHub Issues](https://github.com/montasim/VidQuery/issues) for reproducible bugs and feature requests. Include the browser version, YouTube route type, whether a transcript was available, and the visible error message. Never include an API key, transcript, private question, or full Gemini request in an issue. Report vulnerabilities privately using [SECURITY.md](SECURITY.md). diff --git a/apps/extension/.prettierignore b/apps/extension/.prettierignore new file mode 100644 index 0000000..8eaaa2c --- /dev/null +++ b/apps/extension/.prettierignore @@ -0,0 +1,2 @@ +.output/ +.wxt/ diff --git a/components.json b/apps/extension/components.json similarity index 100% rename from components.json rename to apps/extension/components.json diff --git a/entrypoints/background.ts b/apps/extension/entrypoints/background.ts similarity index 100% rename from entrypoints/background.ts rename to apps/extension/entrypoints/background.ts diff --git a/entrypoints/popup/index.html b/apps/extension/entrypoints/popup/index.html similarity index 100% rename from entrypoints/popup/index.html rename to apps/extension/entrypoints/popup/index.html diff --git a/entrypoints/popup/main.tsx b/apps/extension/entrypoints/popup/main.tsx similarity index 100% rename from entrypoints/popup/main.tsx rename to apps/extension/entrypoints/popup/main.tsx diff --git a/entrypoints/sidepanel/index.html b/apps/extension/entrypoints/sidepanel/index.html similarity index 100% rename from entrypoints/sidepanel/index.html rename to apps/extension/entrypoints/sidepanel/index.html diff --git a/entrypoints/sidepanel/main.tsx b/apps/extension/entrypoints/sidepanel/main.tsx similarity index 100% rename from entrypoints/sidepanel/main.tsx rename to apps/extension/entrypoints/sidepanel/main.tsx diff --git a/entrypoints/youtube.content.ts b/apps/extension/entrypoints/youtube.content.ts similarity index 100% rename from entrypoints/youtube.content.ts rename to apps/extension/entrypoints/youtube.content.ts diff --git a/eslint.config.mjs b/apps/extension/eslint.config.mjs similarity index 100% rename from eslint.config.mjs rename to apps/extension/eslint.config.mjs diff --git a/icons/128.png b/apps/extension/icons/128.png similarity index 100% rename from icons/128.png rename to apps/extension/icons/128.png diff --git a/icons/16.png b/apps/extension/icons/16.png similarity index 100% rename from icons/16.png rename to apps/extension/icons/16.png diff --git a/icons/32.png b/apps/extension/icons/32.png similarity index 100% rename from icons/32.png rename to apps/extension/icons/32.png diff --git a/icons/48.png b/apps/extension/icons/48.png similarity index 100% rename from icons/48.png rename to apps/extension/icons/48.png diff --git a/apps/extension/package.json b/apps/extension/package.json new file mode 100644 index 0000000..a25a0a1 --- /dev/null +++ b/apps/extension/package.json @@ -0,0 +1,58 @@ +{ + "name": "@vidquery/extension", + "version": "2.0.0", + "private": true, + "type": "module", + "description": "A private-by-default Gemini companion for YouTube videos.", + "scripts": { + "dev": "wxt", + "prepare": "wxt prepare", + "build": "wxt build", + "zip": "wxt zip", + "format": "prettier --write .", + "format:check": "prettier --check .", + "lint": "eslint . --max-warnings=0", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest", + "check": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test && pnpm build", + "release:zip": "pnpm check && wxt zip" + }, + "dependencies": { + "@fontsource-variable/instrument-sans": "^5.2.8", + "@fontsource-variable/manrope": "^5.2.8", + "@fontsource/ibm-plex-mono": "^5.2.7", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.544.0", + "radix-ui": "^1.4.3", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", + "tailwind-merge": "^3.3.1", + "zod": "^4.1.12" + }, + "devDependencies": { + "@eslint/js": "^9.39.0", + "@tailwindcss/vite": "^4.1.17", + "@testing-library/jest-dom": "6.9.1", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/chrome": "^0.1.32", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitest/coverage-v8": "^4.0.8", + "@wxt-dev/module-react": "^1.2.0", + "eslint": "^9.39.0", + "fake-indexeddb": "^6.2.4", + "globals": "^16.5.0", + "happy-dom": "^20.0.10", + "prettier": "^3.6.2", + "tailwindcss": "^4.1.17", + "typescript": "^5.9.3", + "typescript-eslint": "^8.46.3", + "vitest": "^4.0.8", + "wxt": "^0.20.11" + } +} diff --git a/src/application/errors.ts b/apps/extension/src/application/errors.ts similarity index 100% rename from src/application/errors.ts rename to apps/extension/src/application/errors.ts diff --git a/src/domain/schemas.ts b/apps/extension/src/domain/schemas.ts similarity index 100% rename from src/domain/schemas.ts rename to apps/extension/src/domain/schemas.ts diff --git a/src/infrastructure/gemini.ts b/apps/extension/src/infrastructure/gemini.ts similarity index 100% rename from src/infrastructure/gemini.ts rename to apps/extension/src/infrastructure/gemini.ts diff --git a/src/infrastructure/storage/chrome-storage.ts b/apps/extension/src/infrastructure/storage/chrome-storage.ts similarity index 100% rename from src/infrastructure/storage/chrome-storage.ts rename to apps/extension/src/infrastructure/storage/chrome-storage.ts diff --git a/src/infrastructure/storage/credential-vault.ts b/apps/extension/src/infrastructure/storage/credential-vault.ts similarity index 100% rename from src/infrastructure/storage/credential-vault.ts rename to apps/extension/src/infrastructure/storage/credential-vault.ts diff --git a/src/infrastructure/youtube-context.ts b/apps/extension/src/infrastructure/youtube-context.ts similarity index 100% rename from src/infrastructure/youtube-context.ts rename to apps/extension/src/infrastructure/youtube-context.ts diff --git a/src/shared/cn.ts b/apps/extension/src/shared/cn.ts similarity index 100% rename from src/shared/cn.ts rename to apps/extension/src/shared/cn.ts diff --git a/src/shared/protocol.ts b/apps/extension/src/shared/protocol.ts similarity index 100% rename from src/shared/protocol.ts rename to apps/extension/src/shared/protocol.ts diff --git a/src/ui/components/brand-mark.tsx b/apps/extension/src/ui/components/brand-mark.tsx similarity index 100% rename from src/ui/components/brand-mark.tsx rename to apps/extension/src/ui/components/brand-mark.tsx diff --git a/src/ui/components/brand.tsx b/apps/extension/src/ui/components/brand.tsx similarity index 100% rename from src/ui/components/brand.tsx rename to apps/extension/src/ui/components/brand.tsx diff --git a/src/ui/components/button.tsx b/apps/extension/src/ui/components/button.tsx similarity index 100% rename from src/ui/components/button.tsx rename to apps/extension/src/ui/components/button.tsx diff --git a/src/ui/components/context-signal.tsx b/apps/extension/src/ui/components/context-signal.tsx similarity index 100% rename from src/ui/components/context-signal.tsx rename to apps/extension/src/ui/components/context-signal.tsx diff --git a/src/ui/components/support-link.tsx b/apps/extension/src/ui/components/support-link.tsx similarity index 100% rename from src/ui/components/support-link.tsx rename to apps/extension/src/ui/components/support-link.tsx diff --git a/src/ui/content/launcher.ts b/apps/extension/src/ui/content/launcher.ts similarity index 100% rename from src/ui/content/launcher.ts rename to apps/extension/src/ui/content/launcher.ts diff --git a/src/ui/globals.css b/apps/extension/src/ui/globals.css similarity index 100% rename from src/ui/globals.css rename to apps/extension/src/ui/globals.css diff --git a/src/ui/popup/popup-app.tsx b/apps/extension/src/ui/popup/popup-app.tsx similarity index 100% rename from src/ui/popup/popup-app.tsx rename to apps/extension/src/ui/popup/popup-app.tsx diff --git a/src/ui/sidepanel/sidepanel-app.tsx b/apps/extension/src/ui/sidepanel/sidepanel-app.tsx similarity index 100% rename from src/ui/sidepanel/sidepanel-app.tsx rename to apps/extension/src/ui/sidepanel/sidepanel-app.tsx diff --git a/tests/helpers/chrome.ts b/apps/extension/tests/helpers/chrome.ts similarity index 100% rename from tests/helpers/chrome.ts rename to apps/extension/tests/helpers/chrome.ts diff --git a/tests/setup.ts b/apps/extension/tests/setup.ts similarity index 100% rename from tests/setup.ts rename to apps/extension/tests/setup.ts diff --git a/tests/unit/credential-vault.test.ts b/apps/extension/tests/unit/credential-vault.test.ts similarity index 100% rename from tests/unit/credential-vault.test.ts rename to apps/extension/tests/unit/credential-vault.test.ts diff --git a/tests/unit/gemini.test.ts b/apps/extension/tests/unit/gemini.test.ts similarity index 100% rename from tests/unit/gemini.test.ts rename to apps/extension/tests/unit/gemini.test.ts diff --git a/tests/unit/launcher.test.ts b/apps/extension/tests/unit/launcher.test.ts similarity index 100% rename from tests/unit/launcher.test.ts rename to apps/extension/tests/unit/launcher.test.ts diff --git a/tests/unit/popup-app.test.tsx b/apps/extension/tests/unit/popup-app.test.tsx similarity index 100% rename from tests/unit/popup-app.test.tsx rename to apps/extension/tests/unit/popup-app.test.tsx diff --git a/tests/unit/sidepanel-app.test.tsx b/apps/extension/tests/unit/sidepanel-app.test.tsx similarity index 100% rename from tests/unit/sidepanel-app.test.tsx rename to apps/extension/tests/unit/sidepanel-app.test.tsx diff --git a/tests/unit/storage.test.ts b/apps/extension/tests/unit/storage.test.ts similarity index 100% rename from tests/unit/storage.test.ts rename to apps/extension/tests/unit/storage.test.ts diff --git a/tests/unit/youtube-context.test.ts b/apps/extension/tests/unit/youtube-context.test.ts similarity index 100% rename from tests/unit/youtube-context.test.ts rename to apps/extension/tests/unit/youtube-context.test.ts diff --git a/tsconfig.json b/apps/extension/tsconfig.json similarity index 100% rename from tsconfig.json rename to apps/extension/tsconfig.json diff --git a/vitest.config.ts b/apps/extension/vitest.config.ts similarity index 100% rename from vitest.config.ts rename to apps/extension/vitest.config.ts diff --git a/wxt.config.ts b/apps/extension/wxt.config.ts similarity index 100% rename from wxt.config.ts rename to apps/extension/wxt.config.ts diff --git a/apps/web/.gitignore b/apps/web/.gitignore new file mode 100644 index 0000000..c507abc --- /dev/null +++ b/apps/web/.gitignore @@ -0,0 +1,16 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +.env +.nitro +.tanstack +.wrangler +.output +.vinxi +__unconfig* +todos.json + +# Local Netlify folder +.netlify diff --git a/apps/web/.prettierignore b/apps/web/.prettierignore new file mode 100644 index 0000000..81e3f55 --- /dev/null +++ b/apps/web/.prettierignore @@ -0,0 +1,4 @@ +package-lock.json +pnpm-lock.yaml +yarn.lock +src/routeTree.gen.ts diff --git a/apps/web/README.md b/apps/web/README.md new file mode 100644 index 0000000..18125aa --- /dev/null +++ b/apps/web/README.md @@ -0,0 +1,28 @@ +# VidQuery website + +The VidQuery product website is a TanStack Start application using React, Tailwind CSS 4, and shadcn components. Netlify builds this workspace independently from the WXT extension. + +## Development + +From the repository root: + +```bash +pnpm install +pnpm dev:web +``` + +The local site runs at `http://localhost:3000`. + +## Validation + +```bash +pnpm check:web +``` + +This generates the TanStack route tree, checks formatting and linting, runs strict TypeScript validation, and produces the Netlify client and SSR builds. + +## Deployment + +Connect the repository to Netlify, leave the base directory at the repository root, and set the package directory to `apps/web`. The committed `netlify.toml` builds `@vidquery/web` and publishes `apps/web/dist/client`. + +The download CTA uses GitHub’s stable latest-release asset URL. Each extension release must therefore include `VidQuery-chrome-unpacked.zip`. diff --git a/apps/web/components.json b/apps/web/components.json new file mode 100644 index 0000000..c270683 --- /dev/null +++ b/apps/web/components.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "radix-nova", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/styles.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "#/components", + "utils": "#/lib/utils", + "ui": "#/components/ui", + "lib": "#/lib", + "hooks": "#/hooks" + }, + "iconLibrary": "lucide", + "rtl": false, + "menuColor": "default", + "menuAccent": "subtle", + "registries": {} +} diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js new file mode 100644 index 0000000..1d627e8 --- /dev/null +++ b/apps/web/eslint.config.js @@ -0,0 +1,24 @@ +// @ts-check + +import { tanstackConfig } from '@tanstack/eslint-config' + +export default [ + ...tanstackConfig, + { + rules: { + 'import/no-cycle': 'off', + 'import/order': 'off', + 'sort-imports': 'off', + '@typescript-eslint/array-type': 'off', + '@typescript-eslint/require-await': 'off', + 'pnpm/json-enforce-catalog': 'off', + }, + }, + { + ignores: [ + 'eslint.config.js', + 'prettier.config.js', + 'src/routeTree.gen.ts', + ], + }, +] diff --git a/apps/web/netlify.toml b/apps/web/netlify.toml new file mode 100644 index 0000000..dc43d86 --- /dev/null +++ b/apps/web/netlify.toml @@ -0,0 +1,12 @@ +[build] + command = "pnpm --filter @vidquery/web build" + publish = "/apps/web/dist/client" + +[build.environment] + NODE_VERSION = "20.19.3" + PNPM_VERSION = "10.10.0" + +[dev] + command = "pnpm --filter @vidquery/web dev" + targetPort = 3000 + port = 8888 diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..442ab53 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,53 @@ +{ + "name": "@vidquery/web", + "private": true, + "type": "module", + "imports": { + "#/*": "./src/*" + }, + "scripts": { + "dev": "vite dev --port 3000", + "generate-routes": "tsr generate", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint", + "format": "prettier --write . && eslint --fix", + "typecheck": "tsc --noEmit", + "check": "tsr generate && prettier --check . && eslint && tsc --noEmit && vite build" + }, + "dependencies": { + "@fontsource-variable/geist": "^5.3.0", + "@fontsource-variable/instrument-sans": "^5.3.0", + "@fontsource-variable/manrope": "^5.3.0", + "@fontsource/ibm-plex-mono": "^5.3.0", + "@tailwindcss/vite": "^4.1.18", + "@tanstack/react-devtools": "latest", + "@tanstack/react-router": "latest", + "@tanstack/react-router-devtools": "latest", + "@tanstack/react-start": "latest", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.28.0", + "radix-ui": "^1.6.7", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "shadcn": "^4.16.1", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.1.18", + "tw-animate-css": "^1.4.0" + }, + "devDependencies": { + "@netlify/vite-plugin-tanstack-start": "^1.2.15", + "@tanstack/devtools-vite": "latest", + "@tanstack/eslint-config": "latest", + "@tanstack/router-cli": "^1.132.0", + "@types/node": "^22.10.2", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.8.0", + "prettier": "^3.8.1", + "typescript": "^6.0.2", + "vite": "^8.0.0" + } +} diff --git a/apps/web/prettier.config.js b/apps/web/prettier.config.js new file mode 100644 index 0000000..c45bb05 --- /dev/null +++ b/apps/web/prettier.config.js @@ -0,0 +1,10 @@ +// @ts-check + +/** @type {import('prettier').Config} */ +const config = { + semi: false, + singleQuote: true, + trailingComma: 'all', +} + +export default config diff --git a/apps/web/public/brand/logo.svg b/apps/web/public/brand/logo.svg new file mode 100644 index 0000000..1ffa7ed --- /dev/null +++ b/apps/web/public/brand/logo.svg @@ -0,0 +1,7 @@ + + + + diff --git a/apps/web/public/prototypes/v1/index.html b/apps/web/public/prototypes/v1/index.html new file mode 100644 index 0000000..50dac05 --- /dev/null +++ b/apps/web/public/prototypes/v1/index.html @@ -0,0 +1,1360 @@ + + + + + + + VidQuery - UI Prototype v1 + + + + + + + + +
+
+
+
+ + + + VidQuery + UI v1 +
+

+ Standalone review prototype · production behavior preserved +

+
+ + +
+
+ +
+
+
+
+
+ +
+ + YouTube +
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+

+ Interface Systems · Lesson 08 +

+

+ Designing calm
product interfaces +

+
+
+ 08:42 / 24:18 +
+ +
+
+
+
+

+ Designing calm product interfaces: a practical walkthrough +

+
+
+
+

Field Notes Studio

+

184K subscribers

+
+ +
+ +
+
+
+ 38K views · 2 days ago +

+ A practical teardown of hierarchy, motion, and information + density. We rebuild one crowded workflow into an interface + that stays clear under pressure. +

+
+
+ + +
+
+
+ +
+
+
+
+
+ +
+

+ Stored on this site +

+

+ Recent videos +

+
+ 3 / 10 +
+
+
+
+
+ 24:18 +
+
+

+ Designing calm product interfaces: a practical walkthrough +

+

+ Field Notes Studio · just now +

+ +
+
+
+
+ GRID18:06 +
+
+

+ A complete guide to responsive CSS Grid +

+

+ Frontend Craft · 2h ago +

+ +
+
+
+
+ ASYNC JS31:44 +
+
+

+ JavaScript promises without the mystery +

+

+ Codefield · yesterday +

+ +
+
+
+
+ Recent video context is kept in YouTube’s local site storage on + this browser. +
+
+
+
+ +
+
+
+
+ Browser content +

+ The active tab is not a supported YouTube video. +

+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+

+ VidQuery +

+

+ Connect Gemini +

+
+
+

+ Add your own API key to ask questions about the video you’re + watching. +

+
+
+
+
+ Get a key ↗ +
+
+ +
+

+ Saved with Chrome sync. Questions and video context go + directly to Google’s Gemini API. +

+
+ + + +
+
+

+ How to use it: + open a YouTube video, select Ask this video, then type a + question. +

+ + + + + Support + +
+
+
+
+
+ + + + diff --git a/apps/web/src/components/brand-mark.tsx b/apps/web/src/components/brand-mark.tsx new file mode 100644 index 0000000..4726a5c --- /dev/null +++ b/apps/web/src/components/brand-mark.tsx @@ -0,0 +1,16 @@ +import { cn } from '#/lib/utils' + +type BrandMarkProps = { + className?: string +} + +export function BrandMark({ className }: BrandMarkProps) { + return ( + + ) +} diff --git a/apps/web/src/components/context-wave.tsx b/apps/web/src/components/context-wave.tsx new file mode 100644 index 0000000..5b7e018 --- /dev/null +++ b/apps/web/src/components/context-wave.tsx @@ -0,0 +1,27 @@ +import { cn } from '#/lib/utils' + +const heights = [7, 16, 10, 19, 12, 6, 15, 9] + +export function ContextWave({ className }: { className?: string }) { + return ( +