Show OAuth provider icons on login buttons and admin OAuth provider UI.
Use simple-icons with existing Lucide fallbacks, and add coverage for built-in provider mappings plus icon rendering branches.
- Add qrcode.react dependency for QR code generation
- Display QR code in PairingModal encoding the pairing URL
- Add countdown timer showing time remaining until code expires
- Auto-expire and stop polling when countdown reaches 0
- All 2809 tests pass
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: add Azure Functions deployment target (v4, Node 22)
- server/entry-azure.ts: Azure Functions v4 handler wrapping the Hono
app via app.http(); uses createLibsqlPlatform for Turso and serves
the SPA from ./dist via @hono/node-server/serve-static
- server/azure-host.json: runtime manifest (extensionBundle v4)
- deploy/azure-functions/main.bicep: idempotent Bicep template
provisioning Storage Account, Consumption plan and Function App;
BETTER_AUTH_SECRET handled separately by the workflow
- .github/workflows/deploy-azure.yml: 8-step workflow (secret check,
checkout, Node setup, az login, Bicep deploy, build, db:migrate,
func publish) with BETTER_AUTH_SECRET generate-if-missing logic
- package.json: build:azure script + @azure/functions dependency
- docs/deploy/azure-functions.md: setup guide covering SP JSON format,
required secrets, and local emulation with func start
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
* fix: address review issues in Azure Functions deploy
- Move BETTER_AUTH_SECRET and APP_URL setup to before func publish
(bootstrap.ts throws on missing secret; any request between publish
and the old secret-set step would have returned 500)
- Remove placeholder appUrl Bicep param; workflow sets APP_URL and
BETTER_AUTH_URL via appsettings after Bicep, before publish
- Fix HttpRequest→Request body handling: construct a proper Web API
Request with body cast and duplex option instead of double-casting
HttpRequest, ensuring POST/PUT/PATCH body-reading routes work
- Add push: branches: [master] trigger + upstream guard to match other
deploy workflow conventions; document the auto-deploy behaviour
- Update docs/deploy/azure-functions.md to reflect the push trigger
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
* ci: re-trigger CI for review fixes
---------
Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
* feat: v2.5.0 T4 — Netlify deployment target
- server/entry-netlify.ts: Netlify Functions v2 (ESM) handler using hono/netlify
adapter; connects to Turso via @libsql/client; skips in-process migrations
(workflow applies them before deploy via drizzle-kit)
- deploy/netlify/netlify.toml: build command, functions directory, SPA fallback redirect
- .github/workflows/deploy-netlify.yml: 8-step workflow — secret guard, tag resolve,
Turso migrations, build, netlify deploy --prod, BETTER_AUTH_SECRET first-deploy, summary
- package.json: add build:netlify script (tsup ESM → netlify/functions)
- docs/deploy/netlify.md: 5-section setup guide covering Turso, site creation,
secrets, deploy trigger, first-boot storage setup, and cost breakdown
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
* fix: address Netlify deploy review blockers
BLOCKER 1 — move BETTER_AUTH_SECRET step before Deploy in workflow so
the function always has the secret set before its first cold start.
BLOCKER 2 — replace inline platform construction in entry-netlify.ts
with createLibsqlPlatform(); removes duplicated db/schema wiring and
re-unifies with the shared factory. migrate() runs at cold start and
is idempotent (~50–100ms) per the workflow's prior drizzle-kit migrate.
BLOCKER 3 — add --external @libsql/client to build:netlify so tsup
leaves the native-binding package for Netlify to resolve; switch
netlify.toml to node_bundler=esbuild so Netlify bundles @libsql/client
from node_modules. Add included_files=["migrations/**"] so the
migrations folder is available in the function zip for migrate().
Minor — replace 2>/dev/null with 2>&1 in deploy step so netlify-cli
errors surface in CI logs instead of being silently swallowed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add Vercel deployment target (Node runtime + Turso)
Adds first-class Vercel support: server/entry-vercel.ts using hono/vercel
handler, deploy/vercel/vercel.json with nodejs22.x function config and SPA
rewrites, build:vercel npm script producing api/entry-vercel.js + dist/,
deploy-vercel GitHub Actions workflow (8-step: secrets check, tag resolve,
checkout, install, migrate, build, link, deploy), and docs/deploy/vercel.md
documenting secrets, quick-start, local dev, and pricing notes.
Edge runtime is explicitly not used — @aws-sdk/client-s3 requires Node APIs.
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
* fix: auto-generate BETTER_AUTH_SECRET on first Vercel deploy
Remove BETTER_AUTH_SECRET from the required secrets check. Add a
dedicated step that detects whether the secret already exists in the
Vercel project env via `vercel env ls production`, then either upserts
the user-supplied GitHub secret, auto-generates one with openssl on
first deploy, or skips if already present. Auto-generation case appends
a backup warning to GITHUB_STEP_SUMMARY. Docs move BETTER_AUTH_SECRET
to Optional Secrets with a note about the auto-gen behaviour.
Matches the existing CF Workers deploy.yml pattern (step 8 contract).
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
---------
Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
* feat: add libSQL (Turso) platform adapter and Docker opt-in
- server/platform/libsql.ts: createLibsqlPlatform() using @libsql/client +
drizzle-orm/libsql; accepts plain env record; async migrate at boot;
authToken optional for file:// URLs
- server/entry-node.ts: select platform at startup — libsql when
TURSO_DATABASE_URL is set, otherwise existing SQLite via createNodePlatform()
- drizzle.config.ts: switch to turso dialect when TURSO_DATABASE_URL is set
- vitest.libsql.config.ts + server/platform/libsql.libsql-test.ts: smoke suite
covering connect, migrations, insert/select against users + storages tables
- package.json: add @libsql/client dependency; add test:libsql script;
externalize @libsql/client in build:node tsup command
- vitest.config.ts: exclude *.libsql-test.ts from coverage
- docs/deploy/docker.md: document Turso opt-in with copy-pasteable
docker-compose snippet
- CONTRIBUTING.md: add Turso migrate path paragraph under Database Migrations
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
* refactor: turn bootstrap.ts into a Platform-accepting factory
- server/bootstrap.ts: replace singleton module-scope script with
exportable createBootstrap(platform) async factory; reads
BETTER_AUTH_SECRET/BETTER_AUTH_URL/TRUSTED_ORIGINS from platform.getEnv
so every future entry (Lambda, Vercel, Netlify, Azure) can reuse it
- server/entry-node.ts: slim down to platform selection + createBootstrap
call; no more duplicate auth/app wiring
- server/dev.ts: thin vite-dev-server entry that creates NodePlatform and
calls createBootstrap; replaces the former default export in bootstrap.ts
- vite.config.ts: update node dev server entry to server/dev.ts
- server/platform/libsql.ts: fix getEnv to check env record before
falling back to process.env, matching the cloudflare.ts pattern
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
* style: apply biome auto-fixes for pre-existing lint issues
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
---------
Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
Docker build ran vite with the cloudflare plugin, which put SPA output
under dist/client/, while entry-node.ts serves from ./dist — causing
404 on every request in the container.
- Add build:node (vite --mode node) so the SPA lands in dist/, and
fold build:server into it as a single command
- Move better-sqlite3 to dependencies (Node runtime needs it; CF
Workers build tree-shakes it out anyway)
- Collapse Dockerfile from 4 stages to 2: one npm ci with a BuildKit
cache mount, then npm prune --omit=dev in place. Drops the
duplicate install and the cross-stage better-sqlite3 copy hack.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add deploy.yml: auto-deploys latest upstream release tag on fork sync
- Support manual trigger with optional version override
- Auto-create D1 database, apply migrations, and set BETTER_AUTH_SECRET
- Only runs on forks (skipped on saltbo/zpan)
- Validate required secrets with clear error message
- Update README with fork + GitHub Actions deploy instructions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: v2.4.0 T7 — Image Host gallery page with FileManager reuse
- Add ImageHostView component that wires FileManager with image-host-specific
config: upload via /api/ihost/images presigned flow, delete with 5s undo toast,
copy URL in raw/Markdown/HTML/BBCode formats, and thumbnail rendering
- Extend FileManager with new capabilities (copyUrl, delete), getThumbnailUrl
prop, onDeleteItems/onCopyUrl callbacks, and viewModeStorageKey for isolated
view-mode persistence per page
- Extend FilesGrid with optional getThumbnailUrl prop: renders lazy-loaded image
thumbnails with FileIcon fallback on error; backward-compatible with Files page
- Extend FileRowActions with Copy URL submenu (raw/Markdown/HTML/BBCode) and a
Delete action separate from Move to Trash; fully backward-compatible
- Extend UploadDropzone with optional uploadFn prop to bypass the default
object-upload flow; Files page behavior unchanged
- Parameterize useViewMode hook with optional storageKey argument
- Add API wrappers: listIhostImages, createIhostImagePresign, confirmIhostImage,
deleteIhostImage with matching tests in api.test.ts
- Add useClipboard hook; refactor navigator.clipboard.writeText usage in
share-dialog.tsx and shares/index.tsx to use the hook
- Add IhostRoute to rpc.ts
- Add ihost.copy.*, ihost.delete.*, ihost.upload.*, ihost.table.* i18n keys
to en.json and zh.json; add common.copied key
Agent-Profile: https://agent-kanban.dev/agents/b724a773425e397c
* fix: use Hono RPC client for ihost API, add presign endpoint, expand test coverage
- Rewrite server/routes/ihost.ts to use method chaining, fixing Hono RPC type
inference (imperative app.post() calls prevented the schema from being typed)
- Extract POST /images/presign as a dedicated typed endpoint (zValidator) for the
browser client; POST /images becomes multipart-only for API-key/PicGo compat
- Frontend: replace raw ihostFetch() with ihostApi RPC calls for all four
wrappers (listIhostImages, createIhostImagePresign, confirmIhostImage,
deleteIhostImage); mime parameter typed as AllowedImageMime
- Update integration tests to use /images/presign for JSON presign cases; adjust
status expectations to 400 (Zod) vs 413/415 (manual checks no longer needed)
- Add unit tests: use-clipboard, image-host-data-source, image-host-view,
file-row-actions, upload-dropzone, use-view-mode custom-key
- Add e2e/image-host.spec.ts: enable feature gate, upload (mocked S3 PUT),
grid→table view switching, copy Markdown URL, delete with Undo, delete permanently
Agent-Profile: https://agent-kanban.dev/agents/b724a773425e397c
* test: improve patch coverage for ihost routes and file-row-actions
Add missing 503/401 integration tests for multipart endpoint and API key
error paths. Extract testable pure functions from file-row-actions.tsx and
image-host-view.tsx and update tests to import from source files.
Agent-Profile: https://agent-kanban.dev/agents/b724a773425e397c
* test: fix coverage cascade, add component rendering tests and branch tests
Revert buildCopyText export which caused file-manager/files-grid/upload-dropzone
to appear in coverage at 0% via transitive imports. Restore inline switch logic
in handleCopyUrl and define buildCopyText locally in the test.
Install @testing-library/react + jsdom, add React plugin to vitest unit project,
and write FileRowActions rendering tests (file-row-actions.render.test.tsx) to
cover JSX branches including Copy URL sub-menu and delete item.
Add missing DELETE 403 (no config) and storage-null branch tests to
ihost.integration.test.ts to cover uncovered branches in ihost.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update ihost.cf-test.ts for multipart-only POST /images endpoint
POST /api/ihost/images now returns 415 for JSON (multipart only).
Add separate test for POST /api/ihost/images/presign returning 403
when image hosting is not enabled.
Agent-Profile: https://agent-kanban.dev/agents/b724a773425e397c
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add --external better-sqlite3 to tsup build to fix ESM runtime error
- Add docker-entrypoint.sh to auto-generate BETTER_AUTH_SECRET if not set
- Persist generated secret to /data/.auth_secret across restarts
- Move image-based compose files to deploy/ directory
- Add deploy/docker-compose.rustfs.yml for ZPan + RustFS setup
- Keep build-from-source docker-compose.yml at project root
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes surfaced by full e2e on the staging preview:
- save-to-drive-dialog.tsx: Radix Select rejects empty-string values.
The "Root" option used value="" and crashed the dialog on open.
Use a '__root__' sentinel in the UI and convert to '' in the payload.
- file-preview.tsx: fetch(..., credentials:'include') failed on the 302
redirect to an R2 presigned URL because R2 returns
Access-Control-Allow-Origin: * which can't combine with credentialed
requests. The presigned URL is already signature-authenticated;
credentials aren't needed for cross-origin. Drop the option.
- share-dialog.tsx: switching to Direct now defaults Expires to "Never"
(matches spec; embeds are meant to live indefinitely).
- file-manager-dialogs.tsx: pass onViewShares to ShareDialog so the
"View in My Shares" button actually renders in the success view.
- folder-browser.tsx: public share empty state was reusing
files.emptyState ("Drop files here or create a folder to get
started."). Use new share.folderEmpty key instead.
- package.json: add db:migrate:d1:staging and db:migrate:d1:prod
scripts so remote D1 migrations are a one-liner. Needed because
the T1 notifications table migration wasn't applied to staging D1,
causing /api/notifications/* to 500 in production.
Follow-up for maintainer: run
npm run db:migrate:d1:staging
npm run db:migrate:d1:prod
to pick up 0010_shares and 0011_notifications on the remote D1s.
Unified email service supporting two drivers configured via system_options:
- SMTP driver using nodemailer (dynamic import for CF Workers compatibility)
- HTTP API driver using fetch (Resend-compatible)
Includes admin API at /api/admin/email-config for GET/PUT/POST test
endpoints with secret masking and discriminated union validation.
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
Tailwind v4 dropped the tailwindcss-animate plugin. Replace with
tw-animate-css so Dialog, Sheet, Dropdown animations work. Also reduce
sheet open/close duration from 500/300ms to 300/200ms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move CLOUDFLARE_ENV logic from ci-build.sh into package.json build
script. Workers Builds can use default build command (npm run build)
without custom Dashboard configuration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Top-level [vars] would override local dev with placeholder values.
BETTER_AUTH_URL and TRUSTED_ORIGINS are not needed in the deploy form —
better-auth auto-infers from the request URL on first deploy.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move BETTER_AUTH_URL and TRUSTED_ORIGINS from .dev.vars.example to
wrangler.toml [vars] so they render as text inputs, not password fields
- Keep only BETTER_AUTH_SECRET in .dev.vars.example (true secret)
- Add cloudflare.bindings descriptions in package.json for deploy form
- Remove unused concurrently dependency
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace Pages Functions with Workers entry (`workers/bootstrap.ts`)
- Add Deploy to Cloudflare button in README
- Integrate `@cloudflare/vite-plugin` for CF dev with HMR
- Integrate `@hono/vite-dev-server` for Node dev with HMR
- `npm run dev` now defaults to CF Workers mode
- Add `run_worker_first = ["/api/*"]` so static assets stay free
- Extract shared Node bootstrap (`server/bootstrap.ts`) for reuse
- Update all docs from Pages to Workers references
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- db:reset script supports --pages flag to reset D1 local database
- Added db:reset:pages npm script
- Added top-level D1 binding in wrangler.toml for local dev
- Standardized dev env var names in .dev.vars
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New users get a 10MB default storage quota (configurable via admin settings).
Admin can set the default in Settings with MB/GB unit selector. Added db:generate,
db:migrate, and db:reset scripts; dev server now reads .dev.vars automatically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes GHSA-gpj5-g38j-94v9 / CVE-2026-39356 (high severity) — drizzle-orm
versions <0.45.2 allowed SQL injection via improperly escaped SQL
identifiers. Our codebase does not actually feed user input through sql
identifier interpolation (all template values are Drizzle column
references or parameterized values), so the CVE was not exploitable,
but upgrading is still the right move:
- Silences the Dependabot high-severity alert.
- Resolves a long-standing peer dependency violation: better-auth 1.6.2
requires drizzle-orm >=0.41.0 and drizzle-kit >=0.31.4, but the
project had drizzle-orm 0.39.3 and drizzle-kit 0.30.6 installed via
lockfile dedup against a stale range.
npm install --save moves both deps to the versions the project
package.json already specifies (^0.45.2 and ^0.31.10). Full quality
gate clean: typecheck, 1000 Node tests, 18 CF Worker tests, biome lint.
A remaining moderate-severity alert (GHSA-67mh-4wv8-2f99 — esbuild dev
server exposure) is carried transitively via drizzle-kit →
@esbuild-kit/esm-loader → esbuild and is not fixable without a
drizzle-kit internal update. The vulnerability only affects esbuild's
dev server, which drizzle-kit does not spin up during its CLI
operations, so there is no real-world exposure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace @svar-ui/react-filemanager with custom file manager built on
@tanstack/react-table, @dnd-kit, and shadcn/ui (ContextMenu, Breadcrumb,
Table, Checkbox, ToggleGroup, Collapsible)
- List view with sortable columns (folders-first), Grid view with card layout
- Right-click context menu, row dropdown actions, drag-and-drop file moving
- Dialogs: rename, new folder, delete confirm, move (with folder picker)
- URL-based path navigation (?path=folder/subfolder), breadcrumb from URL
- Backend: parent field stores materialized path instead of folder ID,
cascade rename/move updates all descendants, self-move protection
- Backend: type filter API (?type=photos|videos|music|documents) for
cross-folder file browsing by MIME type
- Sidebar: My Files with lazy-loading folder tree (auto-expands to current
path), Photos/Videos/Music/Documents categories, Trash
- Settings moved to user avatar dropdown menu
- Migrate pnpm references to npm across docs and config
- i18n: all new keys in en.json and zh.json
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace pnpm with npm in GitHub Actions workflow
- Upgrade Node.js from 20 to 24 in CI, Dockerfile, and engines
- Pin Node 24.14.1 via volta
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>