diff --git a/docs/superpowers/plans/2026-04-27-quick-note-modal-implementation-plan.md b/docs/superpowers/plans/2026-04-27-quick-note-modal-implementation-plan.md
new file mode 100644
index 0000000..573966b
--- /dev/null
+++ b/docs/superpowers/plans/2026-04-27-quick-note-modal-implementation-plan.md
@@ -0,0 +1,405 @@
+# Quick Note Modal Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 在顶部“写文章”按钮左侧新增“速记”入口,提供可拖拽/可全屏的轻量速记弹框,支持上传、手动保存、每用户唯一内容和未保存离开保护。
+
+**Architecture:** 前端在 `AppShell` 挂载速记入口与弹框,弹框拆分为容器层(拖拽、全屏、未保存拦截)和编辑器层(轻量 Vditor + 上传复用)。后端新增用户唯一 `quick_notes` 存储与 `GET/PUT /api/me/quick-note` 接口,服务层统一处理 upsert、长度校验和换行标准化。全链路以 TDD 驱动,按“先失败测试 -> 最小实现 -> 通过测试 -> 提交”小步推进。
+
+**Tech Stack:** Nuxt 4、Vue 3、TypeScript、Bun test、Drizzle ORM(SQLite)
+
+---
+
+## File Structure
+
+**Create**
+- `app/components/QuickNoteModal.vue`:速记弹框容器(近全屏、全屏切换、拖拽、未保存拦截、beforeunload)
+- `app/components/QuickNoteEditor.vue`:轻量编辑器(复用 Vditor 与上传配置)
+- `app/components/quick-note-editor-vditor-config.ts`:速记编辑器 Vditor 轻量配置
+- `app/components/quick-note-modal-state.ts`:速记 dirty/save 状态与交互纯函数
+- `app/components/quick-note-modal-state.test.ts`:状态纯函数测试
+- `server/service/quick-note/index.ts`:速记服务(get/upsert/normalize/validate)
+- `server/service/quick-note/index.test.ts`:速记服务测试
+- `server/api/me/quick-note.get.ts`:获取当前用户速记
+- `server/api/me/quick-note.put.ts`:手动保存当前用户速记
+- `packages/drizzle-pkg/migrations/0012_quick_notes.sql`:新增 quick_notes 表与唯一索引
+
+**Modify**
+- `app/components/AppShell.vue`:新增“速记”按钮并挂载弹框
+- `packages/drizzle-pkg/database/sqlite/schema/content.ts`:新增 `quickNotes` 表定义
+- `packages/drizzle-pkg/lib/schema/content.ts`:导出 `quickNotes`
+- `packages/drizzle-pkg/migrations/meta/_journal.json`:登记新 migration
+- `server/api/me` 下路由索引(若项目需要)用于自动发现 `quick-note` 接口
+
+**Test**
+- `app/components/quick-note-modal-state.test.ts`
+- `app/components/post-body-markdown-editor-vditor-config.test.ts`(可补一条“复用上传格式兼容”断言)
+- `server/service/quick-note/index.test.ts`
+
+---
+
+### Task 1: 数据层建模(每用户唯一 quick note)
+
+**Files:**
+- Modify: `packages/drizzle-pkg/database/sqlite/schema/content.ts`
+- Modify: `packages/drizzle-pkg/lib/schema/content.ts`
+- Create: `packages/drizzle-pkg/migrations/0012_quick_notes.sql`
+- Modify: `packages/drizzle-pkg/migrations/meta/_journal.json`
+
+- [ ] **Step 1: 写失败检查(确认 quick_notes 尚未存在)**
+
+```bash
+rg "quick_notes|quickNotes" packages/drizzle-pkg/database/sqlite/schema/content.ts packages/drizzle-pkg/lib/schema/content.ts
+```
+
+Expected: 无匹配结果。
+
+- [ ] **Step 2: 运行检查确认失败基线**
+
+Run: `rg "quick_notes|quickNotes" packages/drizzle-pkg/database/sqlite/schema/content.ts packages/drizzle-pkg/lib/schema/content.ts`
+Expected: Exit code 1(无命中)
+
+- [ ] **Step 3: 增加表定义与导出**
+
+```ts
+// packages/drizzle-pkg/database/sqlite/schema/content.ts
+export const quickNotes = sqliteTable(
+ "quick_notes",
+ {
+ id: integer().primaryKey(),
+ userId: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
+ content: text().notNull().default(""),
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(),
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().$onUpdate(() => new Date()).notNull(),
+ },
+ (table) => [uniqueIndex("quick_notes_user_id_unique").on(table.userId)],
+);
+```
+
+```ts
+// packages/drizzle-pkg/lib/schema/content.ts
+export { quickNotes } from "../../database/sqlite/schema/content";
+```
+
+```sql
+-- packages/drizzle-pkg/migrations/0012_quick_notes.sql
+CREATE TABLE `quick_notes` (
+ `id` integer PRIMARY KEY NOT NULL,
+ `user_id` integer NOT NULL,
+ `content` text NOT NULL DEFAULT '',
+ `created_at` integer NOT NULL DEFAULT (unixepoch() * 1000),
+ `updated_at` integer NOT NULL DEFAULT (unixepoch() * 1000),
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+CREATE UNIQUE INDEX `quick_notes_user_id_unique` ON `quick_notes` (`user_id`);
+```
+
+- [ ] **Step 4: 生成并校验 schema/migration 一致性**
+
+Run: `cd packages/drizzle-pkg && bun run generate`
+Expected: 生成结果无冲突,`migrations` 元信息可追踪 `0012_quick_notes.sql`
+
+- [ ] **Step 5: 提交**
+
+```bash
+git add packages/drizzle-pkg/database/sqlite/schema/content.ts packages/drizzle-pkg/lib/schema/content.ts packages/drizzle-pkg/migrations/0012_quick_notes.sql packages/drizzle-pkg/migrations/meta/_journal.json
+git commit -m "feat(db): add per-user quick note table"
+```
+
+---
+
+### Task 2: 服务层实现(读取、upsert、校验)
+
+**Files:**
+- Create: `server/service/quick-note/index.ts`
+- Create: `server/service/quick-note/index.test.ts`
+
+- [ ] **Step 1: 写失败测试(无记录返回空、保存后可读取、超长报错)**
+
+```ts
+import { describe, expect, test } from "bun:test";
+import { normalizeQuickNoteContent, validateQuickNoteContentLength } from "./index";
+
+describe("quick-note content guards", () => {
+ test("normalizes CRLF to LF", () => {
+ expect(normalizeQuickNoteContent("a\r\nb")).toBe("a\nb");
+ });
+ test("throws when exceeds limit", () => {
+ expect(() => validateQuickNoteContentLength("x".repeat(200001))).toThrow("速记内容过长");
+ });
+});
+```
+
+- [ ] **Step 2: 运行测试确认失败**
+
+Run: `bun test server/service/quick-note/index.test.ts`
+Expected: FAIL(函数/模块尚未实现)
+
+- [ ] **Step 3: 写最小实现**
+
+```ts
+import { dbGlobal } from "drizzle-pkg/lib/db";
+import { quickNotes } from "drizzle-pkg/lib/schema/content";
+import { eq } from "drizzle-orm";
+import { nextIntegerId } from "#server/utils/sqlite-id";
+
+export const QUICK_NOTE_MAX_LENGTH = 200_000;
+
+export function normalizeQuickNoteContent(content: string) {
+ return content.replace(/\r\n/g, "\n");
+}
+
+export function validateQuickNoteContentLength(content: string) {
+ if (content.length > QUICK_NOTE_MAX_LENGTH) {
+ throw createError({ statusCode: 400, statusMessage: "速记内容过长" });
+ }
+}
+```
+
+并补充 `getQuickNoteByUserId`、`upsertQuickNoteByUserId`,用 `userId` 查询,不存在则 insert,存在则 update。
+
+- [ ] **Step 4: 运行测试确认通过**
+
+Run: `bun test server/service/quick-note/index.test.ts`
+Expected: PASS
+
+- [ ] **Step 5: 提交**
+
+```bash
+git add server/service/quick-note/index.ts server/service/quick-note/index.test.ts
+git commit -m "feat(server): add quick note service with validation"
+```
+
+---
+
+### Task 3: API 层实现(GET/PUT /api/me/quick-note)
+
+**Files:**
+- Create: `server/api/me/quick-note.get.ts`
+- Create: `server/api/me/quick-note.put.ts`
+- Modify: `server/service/quick-note/index.ts`(若需补返回 DTO)
+
+- [ ] **Step 1: 写失败检查(接口文件不存在)**
+
+```bash
+ls server/api/me/quick-note.get.ts server/api/me/quick-note.put.ts
+```
+
+Expected: 报不存在。
+
+- [ ] **Step 2: 运行检查确认失败**
+
+Run: `ls server/api/me/quick-note.get.ts server/api/me/quick-note.put.ts`
+Expected: `No such file or directory`
+
+- [ ] **Step 3: 写最小接口实现**
+
+```ts
+// server/api/me/quick-note.get.ts
+import { getQuickNoteByUserId } from "#server/service/quick-note";
+export default defineWrappedResponseHandler(async (event) => {
+ const user = await event.context.auth.requireUser();
+ const row = await getQuickNoteByUserId(user.id);
+ return R.success({ quickNote: { content: row?.content ?? "", updatedAt: row?.updatedAt ?? null } });
+});
+```
+
+```ts
+// server/api/me/quick-note.put.ts
+import { upsertQuickNoteByUserId } from "#server/service/quick-note";
+export default defineWrappedResponseHandler(async (event) => {
+ const user = await event.context.auth.requireUser();
+ const body = await readBody<{ content: string }>(event);
+ if (typeof body.content !== "string") {
+ throw createError({ statusCode: 400, statusMessage: "content 必须为字符串" });
+ }
+ const row = await upsertQuickNoteByUserId(user.id, body.content);
+ return R.success({ quickNote: { content: row.content, updatedAt: row.updatedAt } });
+});
+```
+
+- [ ] **Step 4: 启动服务并手工验证**
+
+Run: `bun run dev`
+Expected:
+- `GET /api/me/quick-note` 已登录返回 `{ content }`
+- `PUT /api/me/quick-note` 手动保存成功并可再次读回
+
+- [ ] **Step 5: 提交**
+
+```bash
+git add server/api/me/quick-note.get.ts server/api/me/quick-note.put.ts server/service/quick-note/index.ts
+git commit -m "feat(api): add me quick-note get and put endpoints"
+```
+
+---
+
+### Task 4: 前端轻量编辑器与状态机(dirty/save)
+
+**Files:**
+- Create: `app/components/quick-note-editor-vditor-config.ts`
+- Create: `app/components/QuickNoteEditor.vue`
+- Create: `app/components/quick-note-modal-state.ts`
+- Create: `app/components/quick-note-modal-state.test.ts`
+
+- [ ] **Step 1: 写失败测试(dirty 判定、保存后清洁、失败保留草稿)**
+
+```ts
+import { describe, expect, test } from "bun:test";
+import { computeIsDirty } from "./quick-note-modal-state";
+
+describe("quick note modal state", () => {
+ test("is dirty when draft differs from saved", () => {
+ expect(computeIsDirty({ savedContent: "a", draftContent: "b" })).toBe(true);
+ });
+});
+```
+
+- [ ] **Step 2: 运行测试确认失败**
+
+Run: `bun test app/components/quick-note-modal-state.test.ts`
+Expected: FAIL(状态模块未实现)
+
+- [ ] **Step 3: 写最小实现(状态 + 轻量编辑器)**
+
+```ts
+// app/components/quick-note-modal-state.ts
+export function computeIsDirty(input: { savedContent: string; draftContent: string }) {
+ return input.savedContent !== input.draftContent;
+}
+```
+
+```ts
+// app/components/quick-note-editor-vditor-config.ts
+export const QUICK_NOTE_TOOLBAR = ["bold", "italic", "headings", "|", "list", "ordered-list", "|", "link", "upload", "code"];
+```
+
+`QuickNoteEditor.vue` 复用 `post-body-markdown-editor-vditor-bridge.ts` 模式,上传格式复用现有解析逻辑,保留 `onUploadError` 提示。
+
+- [ ] **Step 4: 运行测试确认通过**
+
+Run: `bun test app/components/quick-note-modal-state.test.ts app/components/post-body-markdown-editor-vditor-config.test.ts`
+Expected: PASS
+
+- [ ] **Step 5: 提交**
+
+```bash
+git add app/components/quick-note-editor-vditor-config.ts app/components/QuickNoteEditor.vue app/components/quick-note-modal-state.ts app/components/quick-note-modal-state.test.ts
+git commit -m "feat(app): add lightweight quick-note editor and dirty-state helpers"
+```
+
+---
+
+### Task 5: 弹框容器与入口集成(拖拽/全屏/未保存拦截)
+
+**Files:**
+- Create: `app/components/QuickNoteModal.vue`
+- Modify: `app/components/AppShell.vue`
+
+- [ ] **Step 1: 写失败检查(确认 AppShell 尚无“速记”入口)**
+
+```bash
+rg "速记|QuickNoteModal|quick-note" app/components/AppShell.vue
+```
+
+Expected: 无命中。
+
+- [ ] **Step 2: 运行检查确认失败**
+
+Run: `rg "速记|QuickNoteModal|quick-note" app/components/AppShell.vue`
+Expected: Exit code 1(无命中)
+
+- [ ] **Step 3: 写最小 UI 与交互实现**
+
+```vue
+
+
+ 速记
+
+
+```
+
+```ts
+// QuickNoteModal.vue (核心片段)
+const isFullscreen = ref(false);
+const isDirty = computed(() => draftContent.value !== savedContent.value);
+onMounted(() => window.addEventListener("beforeunload", onBeforeUnload));
+onBeforeUnmount(() => window.removeEventListener("beforeunload", onBeforeUnload));
+```
+
+并实现:
+- 默认近全屏(保留边距)
+- 右上角按钮切换全屏/退出全屏
+- 非全屏标题栏拖拽
+- 关闭弹框时 dirty 二次确认
+
+- [ ] **Step 4: 启动前端手测关键交互**
+
+Run: `bun run dev`
+Expected:
+- “速记”按钮在“写文章”左侧可见
+- 打开即近全屏
+- 全屏切换可用
+- 未保存关闭时弹确认框
+- 未保存刷新/离页触发浏览器原生提醒
+
+- [ ] **Step 5: 提交**
+
+```bash
+git add app/components/AppShell.vue app/components/QuickNoteModal.vue
+git commit -m "feat(ui): add quick-note modal entry with drag/fullscreen and unsaved guards"
+```
+
+---
+
+### Task 6: 端到端回归与质量闸门
+
+**Files:**
+- Modify: `docs/superpowers/specs/2026-04-27-quick-note-modal-design.md`(仅在验收结论需要补充时)
+
+- [ ] **Step 1: 运行自动化测试**
+
+Run: `bun test app/components/quick-note-modal-state.test.ts server/service/quick-note/index.test.ts app/components/post-body-markdown-editor-vditor-config.test.ts`
+Expected: PASS
+
+- [ ] **Step 2: 运行 lint(仅本次变更范围)**
+
+Run: `bun run lint`
+Expected: 无新增错误(允许存在历史告警,但不能新增)
+
+- [ ] **Step 3: 手工验收清单**
+
+Run: `bun run dev`
+Expected:
+- 每个登录用户只有一条速记内容(切账号互不影响)
+- 仅手动保存写入服务端
+- 未保存保护在“关闭弹框”和“离开页面”两处都生效
+- 上传可用,插入行为与文章编辑链路一致
+
+- [ ] **Step 4: 最终提交**
+
+```bash
+git add app/components server/api/me server/service/quick-note packages/drizzle-pkg
+git commit -m "feat(quick-note): implement per-user quick note modal with manual save"
+```
+
+- [ ] **Step 5: 输出验证证据**
+
+Run: `git show --stat --oneline -1`
+Expected: 展示 quick-note 相关文件变更清单,便于 PR 描述直接引用。
+
+---
+
+## Self-Review
+
+1. **Spec coverage:** 已覆盖入口位置、近全屏+全屏切换、拖拽、轻量编辑器+上传、用户唯一存储、手动保存、双重未保存提醒、测试与验收。
+2. **Placeholder scan:** 无 TBD/TODO/“后续补充”;每个任务均给出具体路径、命令、预期输出和最小代码片段。
+3. **Type consistency:** 前后统一使用 `quick-note` 路由命名、`savedContent/draftContent/isDirty` 状态命名、`GET/PUT /api/me/quick-note` 接口约定。
diff --git a/docs/superpowers/specs/2026-04-27-quick-note-modal-design.md b/docs/superpowers/specs/2026-04-27-quick-note-modal-design.md
new file mode 100644
index 0000000..569a5e9
--- /dev/null
+++ b/docs/superpowers/specs/2026-04-27-quick-note-modal-design.md
@@ -0,0 +1,147 @@
+# Quick Note Modal Design
+
+## Background
+
+Current writing flow is centered on article creation. The user needs a faster way to jot down temporary or reusable notes without opening the full post creation flow. The quick note should be accessible near the top "Write Article" button and support a lightweight editing experience with media upload.
+
+## Goals
+
+- Add a `Quick Note` entry to the left side of the top `Write Article` button.
+- Open a draggable modal that defaults to near-fullscreen and can switch to true fullscreen.
+- Provide a lightweight editor with upload support.
+- Store one unique quick note per authenticated user.
+- Use manual save only.
+- Protect unsaved changes when closing the modal or leaving the page.
+
+## Non-goals
+
+- No auto-save in this iteration.
+- No multi-note list, categorization, or version history.
+- No public sharing capability.
+- No cross-user collaboration.
+
+## User Experience
+
+### Entry and Open
+
+- A `Quick Note` button appears to the left of `Write Article`.
+- Clicking `Quick Note` opens the modal and loads the current user's note from backend.
+- If no note exists, editor opens with empty content.
+
+### Modal Behavior
+
+- Default state is near-fullscreen (small viewport margins).
+- Modal is draggable by title bar when not in fullscreen.
+- Top-right button toggles `Fullscreen` and `Exit Fullscreen`.
+- Fullscreen mode disables dragging.
+- Exiting fullscreen restores draggable mode and previous near-fullscreen frame behavior.
+
+### Editor Behavior
+
+- Use a lightweight markdown editor configuration to reduce startup and UI complexity.
+- Keep upload capability aligned with existing article editor upload flow.
+- Editing updates local draft only until user clicks `Save`.
+
+### Save and Unsaved Protection
+
+- Save button triggers manual persistence.
+- Unsaved state (`isDirty`) is determined by comparing `draftContent` against `savedContent`.
+- On modal close attempt with unsaved content, show confirmation dialog:
+ - Confirm: close and discard unsaved changes in current session.
+ - Cancel: keep modal open.
+- On page leave/refresh/navigation with unsaved content while modal is open, trigger browser `beforeunload` confirmation.
+- After successful save, dirty state is cleared.
+
+## Architecture and Components
+
+### Frontend
+
+- Add `Quick Note` trigger in the top action area where `Write Article` is rendered.
+- Add `QuickNoteModal.vue`:
+ - Modal shell, header controls, drag behavior, fullscreen toggle, close interception.
+ - Unsaved-close confirmation handling.
+ - Register/unregister `beforeunload` guard while open.
+- Add `QuickNoteEditor.vue`:
+ - Lightweight editor wrapper.
+ - Minimal toolbar and markdown input area.
+ - Upload hook integration by reusing existing editor upload bridge/uploader path.
+- State model inside modal:
+ - `savedContent: string`
+ - `draftContent: string`
+ - `isDirty: boolean` (derived)
+ - `isSaving: boolean`
+ - `isLoading: boolean`
+
+### Backend
+
+- Introduce persistent user-unique quick note record.
+- Data model (`quick_notes` or project-consistent naming):
+ - `id`
+ - `user_id` (unique index)
+ - `content` (text)
+ - `created_at`
+ - `updated_at`
+- Add API endpoints:
+ - `GET /api/me/quick-note`
+ - Returns current user's note content.
+ - Returns empty content if no record exists.
+ - `PUT /api/me/quick-note`
+ - Accepts `content`.
+ - Performs upsert by `user_id`.
+ - Returns saved content and timestamps.
+
+## Data and Validation Rules
+
+- Endpoint access requires authenticated `me` context.
+- Content must be string.
+- Enforce max content length (proposed: 200,000 chars) to prevent abuse and oversized payloads.
+- Normalize line endings server-side to keep storage consistent.
+
+## Error Handling
+
+- Load failure: show inline load error with retry option.
+- Save failure: keep `draftContent` untouched, preserve dirty state, and show save error toast/message.
+- Concurrent upsert conflicts (rare): service layer retries update path once after conflict.
+
+## Testing Strategy
+
+### Frontend Unit Tests
+
+- `QuickNoteModal` open/close, fullscreen toggle, drag enable/disable switching.
+- Dirty-state derived behavior for save success/failure.
+- Close interception when dirty.
+- `beforeunload` guard registration lifecycle and dirty-condition trigger.
+- `QuickNoteEditor` upload insertion path and save event behavior.
+
+### Backend Unit Tests
+
+- `GET /api/me/quick-note` with existing note.
+- `GET /api/me/quick-note` with no existing note.
+- `PUT /api/me/quick-note` create path and update path.
+- Validation rejection for non-string or oversized payload.
+- Auth rejection for unauthenticated access.
+
+### Manual Acceptance
+
+- Quick Note button opens modal in near-fullscreen.
+- Fullscreen/exit button works as expected.
+- Draggable behavior works only outside fullscreen.
+- Unsaved close prompt appears on close attempts.
+- Browser leave prompt appears on refresh/navigate with unsaved changes.
+- Save persists content; reopening shows latest saved state.
+- Upload works in quick note editor and follows existing media behavior.
+
+## Rollout and Compatibility
+
+- Feature can ship without affecting existing article editor behavior.
+- No migration impact on posts data.
+- New quick note storage is isolated to authenticated user scope.
+
+## Open Decisions (Resolved)
+
+- Uniqueness scope: per authenticated user.
+- Save strategy: manual only.
+- Unsaved reminders: modal close + page leave.
+- Default view: near-fullscreen.
+- Fullscreen trigger: top-right toggle button.
+- Editor approach: lightweight editor with upload retained.