f6dfd09d82
Replaces the URL-only placeholder extension with a full MV3 page-clipper
that builds a self-contained EPUB on the user's machine and uploads it
to the inbox.
- Captures the rendered DOM in a content script, then runs Readability,
asset bundling, and EPUB build through the shared
`convertToEpub({kind: 'page'})` pipeline inside a Chrome offscreen
document (the SW lacks DOMParser).
- Uploads the resulting EPUB directly from the offscreen page to the
new `POST /api/send/inbox/file` endpoint — keeps the bytes in one
realm because `runtime.sendMessage` JSON-serialises ArrayBuffer to
`{}` between extension contexts.
- Adds a long-lived Port + ping handshake between SW, offscreen, and
the on-demand capture content script so neither idle-eviction nor
load-order races can hang the popup.
- Localised popup, badge feedback, key-as-content i18n (`_('English source')`)
with an extract script that seeds locale stubs from i18n-langs.json
and writes a static-imports map for the runtime. All 33 locales
fully translated.
- Server: `pages/api/send/inbox/file.ts` accepts a raw EPUB body
(Content-Type: application/epub+zip), enforces the inbox pending cap,
stores to the existing send-inbox R2 bucket as `kind='file'`.
`assetBundler` now sets `credentials: 'include'` in the non-Tauri
branch so the extension SW carries paywalled-CDN cookies.
- 47 vitest cases for the extension shell (upload, badge, auth, lazy,
popup state machine, auth-bridge token sync) + 8 cases for the new
server endpoint. CI's `test_web_app` invokes both via the extended
`test:pr:web` plus a `build-browser-ext` step that catches webpack
alias / Tauri-stub regressions.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
177 lines
5.3 KiB
JSON
177 lines
5.3 KiB
JSON
{
|
|
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
|
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
|
|
"files": {
|
|
"ignoreUnknown": true,
|
|
"includes": [
|
|
"**",
|
|
"!**/node_modules/**",
|
|
"!**/packages/**",
|
|
"!**/.claude/**",
|
|
"!**/gen/**",
|
|
"!**/autogenerated/**",
|
|
"!**/schemas/**"
|
|
]
|
|
},
|
|
"formatter": {
|
|
"enabled": true,
|
|
"indentStyle": "space",
|
|
"indentWidth": 2,
|
|
"lineWidth": 100,
|
|
"lineEnding": "lf",
|
|
"includes": [
|
|
"**",
|
|
"!**/.next/**",
|
|
"!**/.open-next/**",
|
|
"!**/.build/**",
|
|
"!**/.tauri/**",
|
|
"!**/.wrangler/**",
|
|
"!**/.vercel/**",
|
|
"!**/out/**",
|
|
"!**/build/**",
|
|
"!**/dist/**",
|
|
"!**/target/**",
|
|
"!**/fastlane/**",
|
|
"!apps/readest-app/public/*.js",
|
|
"!apps/readest-app/public/vendor/**",
|
|
"!apps/readest-app/src-tauri/plugins/tauri-plugin-turso/**",
|
|
"!apps/readest-app/src-tauri/plugins/tauri-plugin-webview-upgrade/**",
|
|
"!**/*.mjs",
|
|
"!**/*.cjs",
|
|
"!**/*.mts",
|
|
"!pnpm-lock.yaml"
|
|
]
|
|
},
|
|
"assist": { "enabled": false },
|
|
"css": {
|
|
"parser": { "tailwindDirectives": true },
|
|
"linter": { "enabled": false },
|
|
"formatter": { "enabled": true, "quoteStyle": "single" }
|
|
},
|
|
"linter": {
|
|
"enabled": true,
|
|
"includes": [
|
|
"**",
|
|
"!apps/readest-app/.next/**",
|
|
"!apps/readest-app/.open-next/**",
|
|
"!apps/readest-app/.wrangler/**",
|
|
"!apps/readest-app/.claude/**",
|
|
"!apps/readest-app/dist/**",
|
|
"!apps/readest-app/out/**",
|
|
"!apps/readest-app/build/**",
|
|
"!apps/readest-app/public/**",
|
|
"!apps/readest-app/src-tauri/**",
|
|
"!apps/readest-app/extensions/**/dist/**",
|
|
"!apps/readest-app/extensions/**/node_modules/**",
|
|
"!apps/readest-app/next-env.d.ts",
|
|
"!apps/readest-app/i18next-scanner.config.cjs"
|
|
],
|
|
"rules": {
|
|
"recommended": true,
|
|
"a11y": {
|
|
"recommended": true,
|
|
"useKeyWithClickEvents": "off",
|
|
"useKeyWithMouseEvents": "off",
|
|
"noSvgWithoutTitle": "off",
|
|
"noLabelWithoutControl": "off",
|
|
"useSemanticElements": "off",
|
|
"noAriaHiddenOnFocusable": "off",
|
|
"noInteractiveElementToNoninteractiveRole": "off",
|
|
"noNoninteractiveElementToInteractiveRole": "off",
|
|
"noNoninteractiveElementInteractions": "off",
|
|
"noStaticElementInteractions": "off",
|
|
"noNoninteractiveTabindex": "off",
|
|
"useButtonType": "off",
|
|
"useAriaPropsSupportedByRole": "off",
|
|
"useFocusableInteractive": "off",
|
|
"noAutofocus": "off"
|
|
},
|
|
"complexity": {
|
|
"noForEach": "off",
|
|
"noStaticOnlyClass": "off",
|
|
"noUselessSwitchCase": "off",
|
|
"noUselessFragments": "off",
|
|
"noUselessCatch": "off",
|
|
"useLiteralKeys": "off",
|
|
"useOptionalChain": "off",
|
|
"noThisInStatic": "off",
|
|
"useArrowFunction": "off",
|
|
"noUselessEscapeInRegex": "off"
|
|
},
|
|
"correctness": {
|
|
"noUnusedVariables": "warn",
|
|
"noUnusedImports": "error",
|
|
"useExhaustiveDependencies": "off",
|
|
"useHookAtTopLevel": "error",
|
|
"useJsxKeyInIterable": "error",
|
|
"noChildrenProp": "error",
|
|
"noNextAsyncClientComponent": "warn",
|
|
"noSwitchDeclarations": "off",
|
|
"noUndeclaredVariables": "off",
|
|
"noEmptyCharacterClassInRegex": "off",
|
|
"useParseIntRadix": "off",
|
|
"noEmptyPattern": "off"
|
|
},
|
|
"nursery": {
|
|
"noBeforeInteractiveScriptOutsideDocument": "warn",
|
|
"noDuplicateEnumValues": "error",
|
|
"noSyncScripts": "warn",
|
|
"useInlineScriptId": "error"
|
|
},
|
|
"performance": {
|
|
"noImgElement": "off",
|
|
"noUnwantedPolyfillio": "warn",
|
|
"useGoogleFontPreconnect": "warn"
|
|
},
|
|
"security": {
|
|
"noBlankTarget": "off",
|
|
"noDangerouslySetInnerHtmlWithChildren": "off",
|
|
"noDangerouslySetInnerHtml": "off"
|
|
},
|
|
"style": {
|
|
"noNonNullAssertion": "off",
|
|
"useImportType": "off",
|
|
"noParameterAssign": "off",
|
|
"useDefaultParameterLast": "off",
|
|
"noUselessElse": "off",
|
|
"noHeadElement": "warn",
|
|
"noCommonJs": "off",
|
|
"useFilenamingConvention": "off",
|
|
"useNamingConvention": "off",
|
|
"noUnusedTemplateLiteral": "off",
|
|
"useTemplate": "off",
|
|
"useExponentiationOperator": "off",
|
|
"useNodejsImportProtocol": "off"
|
|
},
|
|
"suspicious": {
|
|
"noExplicitAny": "error",
|
|
"noArrayIndexKey": "off",
|
|
"noAssignInExpressions": "off",
|
|
"noDoubleEquals": "off",
|
|
"noDocumentImportInPage": "error",
|
|
"noHeadImportInDocument": "error",
|
|
"useGoogleFontDisplay": "warn",
|
|
"noCommentText": "error",
|
|
"noDuplicateJsxProps": "error",
|
|
"noAsyncPromiseExecutor": "off",
|
|
"noImplicitAnyLet": "off",
|
|
"noControlCharactersInRegex": "off",
|
|
"noEmptyBlockStatements": "off",
|
|
"useIterableCallbackReturn": "off",
|
|
"noGlobalIsNan": "off",
|
|
"noConfusingVoidType": "off",
|
|
"noConstEnum": "off"
|
|
}
|
|
}
|
|
},
|
|
"javascript": {
|
|
"globals": ["React"],
|
|
"formatter": {
|
|
"quoteStyle": "single",
|
|
"jsxQuoteStyle": "single",
|
|
"trailingCommas": "all",
|
|
"semicolons": "always"
|
|
}
|
|
}
|
|
}
|