aboutsummaryrefslogtreecommitdiffstats
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/.gitignore24
-rw-r--r--frontend/.prettierrc7
-rw-r--r--frontend/README.md73
-rw-r--r--frontend/coverage/base.css224
-rw-r--r--frontend/coverage/block-navigation.js87
-rw-r--r--frontend/coverage/clover.xml385
-rw-r--r--frontend/coverage/coverage-final.json15
-rw-r--r--frontend/coverage/favicon.pngbin445 -> 0 bytes
-rw-r--r--frontend/coverage/index.html131
-rw-r--r--frontend/coverage/prettify.css1
-rw-r--r--frontend/coverage/prettify.js2
-rw-r--r--frontend/coverage/sort-arrow-sprite.pngbin138 -> 0 bytes
-rw-r--r--frontend/coverage/sorter.js210
-rw-r--r--frontend/coverage/src/App.css.html478
-rw-r--r--frontend/coverage/src/App.tsx.html505
-rw-r--r--frontend/coverage/src/components/FeedItem.css.html526
-rw-r--r--frontend/coverage/src/components/FeedItem.tsx.html430
-rw-r--r--frontend/coverage/src/components/FeedItems.css.html151
-rw-r--r--frontend/coverage/src/components/FeedItems.tsx.html838
-rw-r--r--frontend/coverage/src/components/FeedList.css.html724
-rw-r--r--frontend/coverage/src/components/FeedList.tsx.html721
-rw-r--r--frontend/coverage/src/components/Login.css.html274
-rw-r--r--frontend/coverage/src/components/Login.tsx.html286
-rw-r--r--frontend/coverage/src/components/Settings.css.html802
-rw-r--r--frontend/coverage/src/components/Settings.tsx.html793
-rw-r--r--frontend/coverage/src/components/index.html266
-rw-r--r--frontend/coverage/src/index.html146
-rw-r--r--frontend/eslint.config.js32
-rw-r--r--frontend/index.html13
-rw-r--r--frontend/package-lock.json4469
-rw-r--r--frontend/package.json45
-rw-r--r--frontend/playwright.config.ts25
-rw-r--r--frontend/public/vite.svg1
-rw-r--r--frontend/src/App.css136
-rw-r--r--frontend/src/App.test.tsx60
-rw-r--r--frontend/src/App.tsx140
-rw-r--r--frontend/src/Navigation.test.tsx123
-rw-r--r--frontend/src/assets/react.svg1
-rw-r--r--frontend/src/components/FeedItem.css158
-rw-r--r--frontend/src/components/FeedItem.test.tsx80
-rw-r--r--frontend/src/components/FeedItem.tsx90
-rw-r--r--frontend/src/components/FeedItems.css23
-rw-r--r--frontend/src/components/FeedItems.test.tsx250
-rw-r--r--frontend/src/components/FeedItems.tsx313
-rw-r--r--frontend/src/components/FeedList.css225
-rw-r--r--frontend/src/components/FeedList.test.tsx230
-rw-r--r--frontend/src/components/FeedList.tsx279
-rw-r--r--frontend/src/components/FeedListVariants.css342
-rw-r--r--frontend/src/components/Login.css63
-rw-r--r--frontend/src/components/Login.test.tsx93
-rw-r--r--frontend/src/components/Login.tsx67
-rw-r--r--frontend/src/components/Settings.css240
-rw-r--r--frontend/src/components/Settings.test.tsx207
-rw-r--r--frontend/src/components/Settings.tsx236
-rw-r--r--frontend/src/components/TagView.test.tsx93
-rw-r--r--frontend/src/index.css158
-rw-r--r--frontend/src/main.tsx10
-rw-r--r--frontend/src/setupTests.ts40
-rw-r--r--frontend/src/types.ts24
-rw-r--r--frontend/src/utils.ts32
-rw-r--r--frontend/tests/auth.spec.ts167
-rw-r--r--frontend/tests/crawl.spec.ts32
-rw-r--r--frontend/tests/e2e.spec.ts64
-rw-r--r--frontend/tests/font.spec.ts31
-rw-r--r--frontend/tests/mocked-api.spec.ts167
-rw-r--r--frontend/tsconfig.app.json27
-rw-r--r--frontend/tsconfig.json4
-rw-r--r--frontend/tsconfig.node.json24
-rw-r--r--frontend/vite.config.ts15
-rw-r--r--frontend/vitest.config.ts11
70 files changed, 0 insertions, 16939 deletions
diff --git a/frontend/.gitignore b/frontend/.gitignore
deleted file mode 100644
index a547bf3..0000000
--- a/frontend/.gitignore
+++ /dev/null
@@ -1,24 +0,0 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-lerna-debug.log*
-
-node_modules
-dist
-dist-ssr
-*.local
-
-# Editor directories and files
-.vscode/*
-!.vscode/extensions.json
-.idea
-.DS_Store
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?
diff --git a/frontend/.prettierrc b/frontend/.prettierrc
deleted file mode 100644
index 1f4c4bb..0000000
--- a/frontend/.prettierrc
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "semi": true,
- "singleQuote": true,
- "tabWidth": 2,
- "trailingComma": "es5",
- "printWidth": 100
-}
diff --git a/frontend/README.md b/frontend/README.md
deleted file mode 100644
index c987b94..0000000
--- a/frontend/README.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# React + TypeScript + Vite
-
-This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
-
-Currently, two official plugins are available:
-
-- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
-- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
-
-## React Compiler
-
-The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
-
-## Expanding the ESLint configuration
-
-If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
-
-```js
-export default defineConfig([
- globalIgnores(['dist']),
- {
- files: ['**/*.{ts,tsx}'],
- extends: [
- // Other configs...
-
- // Remove tseslint.configs.recommended and replace with this
- tseslint.configs.recommendedTypeChecked,
- // Alternatively, use this for stricter rules
- tseslint.configs.strictTypeChecked,
- // Optionally, add this for stylistic rules
- tseslint.configs.stylisticTypeChecked,
-
- // Other configs...
- ],
- languageOptions: {
- parserOptions: {
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
- tsconfigRootDir: import.meta.dirname,
- },
- // other options...
- },
- },
-]);
-```
-
-You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
-
-```js
-// eslint.config.js
-import reactX from 'eslint-plugin-react-x';
-import reactDom from 'eslint-plugin-react-dom';
-
-export default defineConfig([
- globalIgnores(['dist']),
- {
- files: ['**/*.{ts,tsx}'],
- extends: [
- // Other configs...
- // Enable lint rules for React
- reactX.configs['recommended-typescript'],
- // Enable lint rules for React DOM
- reactDom.configs.recommended,
- ],
- languageOptions: {
- parserOptions: {
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
- tsconfigRootDir: import.meta.dirname,
- },
- // other options...
- },
- },
-]);
-```
diff --git a/frontend/coverage/base.css b/frontend/coverage/base.css
deleted file mode 100644
index f418035..0000000
--- a/frontend/coverage/base.css
+++ /dev/null
@@ -1,224 +0,0 @@
-body, html {
- margin:0; padding: 0;
- height: 100%;
-}
-body {
- font-family: Helvetica Neue, Helvetica, Arial;
- font-size: 14px;
- color:#333;
-}
-.small { font-size: 12px; }
-*, *:after, *:before {
- -webkit-box-sizing:border-box;
- -moz-box-sizing:border-box;
- box-sizing:border-box;
- }
-h1 { font-size: 20px; margin: 0;}
-h2 { font-size: 14px; }
-pre {
- font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
- margin: 0;
- padding: 0;
- -moz-tab-size: 2;
- -o-tab-size: 2;
- tab-size: 2;
-}
-a { color:#0074D9; text-decoration:none; }
-a:hover { text-decoration:underline; }
-.strong { font-weight: bold; }
-.space-top1 { padding: 10px 0 0 0; }
-.pad2y { padding: 20px 0; }
-.pad1y { padding: 10px 0; }
-.pad2x { padding: 0 20px; }
-.pad2 { padding: 20px; }
-.pad1 { padding: 10px; }
-.space-left2 { padding-left:55px; }
-.space-right2 { padding-right:20px; }
-.center { text-align:center; }
-.clearfix { display:block; }
-.clearfix:after {
- content:'';
- display:block;
- height:0;
- clear:both;
- visibility:hidden;
- }
-.fl { float: left; }
-@media only screen and (max-width:640px) {
- .col3 { width:100%; max-width:100%; }
- .hide-mobile { display:none!important; }
-}
-
-.quiet {
- color: #7f7f7f;
- color: rgba(0,0,0,0.5);
-}
-.quiet a { opacity: 0.7; }
-
-.fraction {
- font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
- font-size: 10px;
- color: #555;
- background: #E8E8E8;
- padding: 4px 5px;
- border-radius: 3px;
- vertical-align: middle;
-}
-
-div.path a:link, div.path a:visited { color: #333; }
-table.coverage {
- border-collapse: collapse;
- margin: 10px 0 0 0;
- padding: 0;
-}
-
-table.coverage td {
- margin: 0;
- padding: 0;
- vertical-align: top;
-}
-table.coverage td.line-count {
- text-align: right;
- padding: 0 5px 0 20px;
-}
-table.coverage td.line-coverage {
- text-align: right;
- padding-right: 10px;
- min-width:20px;
-}
-
-table.coverage td span.cline-any {
- display: inline-block;
- padding: 0 5px;
- width: 100%;
-}
-.missing-if-branch {
- display: inline-block;
- margin-right: 5px;
- border-radius: 3px;
- position: relative;
- padding: 0 4px;
- background: #333;
- color: yellow;
-}
-
-.skip-if-branch {
- display: none;
- margin-right: 10px;
- position: relative;
- padding: 0 4px;
- background: #ccc;
- color: white;
-}
-.missing-if-branch .typ, .skip-if-branch .typ {
- color: inherit !important;
-}
-.coverage-summary {
- border-collapse: collapse;
- width: 100%;
-}
-.coverage-summary tr { border-bottom: 1px solid #bbb; }
-.keyline-all { border: 1px solid #ddd; }
-.coverage-summary td, .coverage-summary th { padding: 10px; }
-.coverage-summary tbody { border: 1px solid #bbb; }
-.coverage-summary td { border-right: 1px solid #bbb; }
-.coverage-summary td:last-child { border-right: none; }
-.coverage-summary th {
- text-align: left;
- font-weight: normal;
- white-space: nowrap;
-}
-.coverage-summary th.file { border-right: none !important; }
-.coverage-summary th.pct { }
-.coverage-summary th.pic,
-.coverage-summary th.abs,
-.coverage-summary td.pct,
-.coverage-summary td.abs { text-align: right; }
-.coverage-summary td.file { white-space: nowrap; }
-.coverage-summary td.pic { min-width: 120px !important; }
-.coverage-summary tfoot td { }
-
-.coverage-summary .sorter {
- height: 10px;
- width: 7px;
- display: inline-block;
- margin-left: 0.5em;
- background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
-}
-.coverage-summary .sorted .sorter {
- background-position: 0 -20px;
-}
-.coverage-summary .sorted-desc .sorter {
- background-position: 0 -10px;
-}
-.status-line { height: 10px; }
-/* yellow */
-.cbranch-no { background: yellow !important; color: #111; }
-/* dark red */
-.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
-.low .chart { border:1px solid #C21F39 }
-.highlighted,
-.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
- background: #C21F39 !important;
-}
-/* medium red */
-.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
-/* light red */
-.low, .cline-no { background:#FCE1E5 }
-/* light green */
-.high, .cline-yes { background:rgb(230,245,208) }
-/* medium green */
-.cstat-yes { background:rgb(161,215,106) }
-/* dark green */
-.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
-.high .chart { border:1px solid rgb(77,146,33) }
-/* dark yellow (gold) */
-.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
-.medium .chart { border:1px solid #f9cd0b; }
-/* light yellow */
-.medium { background: #fff4c2; }
-
-.cstat-skip { background: #ddd; color: #111; }
-.fstat-skip { background: #ddd; color: #111 !important; }
-.cbranch-skip { background: #ddd !important; color: #111; }
-
-span.cline-neutral { background: #eaeaea; }
-
-.coverage-summary td.empty {
- opacity: .5;
- padding-top: 4px;
- padding-bottom: 4px;
- line-height: 1;
- color: #888;
-}
-
-.cover-fill, .cover-empty {
- display:inline-block;
- height: 12px;
-}
-.chart {
- line-height: 0;
-}
-.cover-empty {
- background: white;
-}
-.cover-full {
- border-right: none !important;
-}
-pre.prettyprint {
- border: none !important;
- padding: 0 !important;
- margin: 0 !important;
-}
-.com { color: #999 !important; }
-.ignore-none { color: #999; font-weight: normal; }
-
-.wrapper {
- min-height: 100%;
- height: auto !important;
- height: 100%;
- margin: 0 auto -48px;
-}
-.footer, .push {
- height: 48px;
-}
diff --git a/frontend/coverage/block-navigation.js b/frontend/coverage/block-navigation.js
deleted file mode 100644
index 530d1ed..0000000
--- a/frontend/coverage/block-navigation.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/* eslint-disable */
-var jumpToCode = (function init() {
- // Classes of code we would like to highlight in the file view
- var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
-
- // Elements to highlight in the file listing view
- var fileListingElements = ['td.pct.low'];
-
- // We don't want to select elements that are direct descendants of another match
- var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
-
- // Selector that finds elements on the page to which we can jump
- var selector =
- fileListingElements.join(', ') +
- ', ' +
- notSelector +
- missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
-
- // The NodeList of matching elements
- var missingCoverageElements = document.querySelectorAll(selector);
-
- var currentIndex;
-
- function toggleClass(index) {
- missingCoverageElements
- .item(currentIndex)
- .classList.remove('highlighted');
- missingCoverageElements.item(index).classList.add('highlighted');
- }
-
- function makeCurrent(index) {
- toggleClass(index);
- currentIndex = index;
- missingCoverageElements.item(index).scrollIntoView({
- behavior: 'smooth',
- block: 'center',
- inline: 'center'
- });
- }
-
- function goToPrevious() {
- var nextIndex = 0;
- if (typeof currentIndex !== 'number' || currentIndex === 0) {
- nextIndex = missingCoverageElements.length - 1;
- } else if (missingCoverageElements.length > 1) {
- nextIndex = currentIndex - 1;
- }
-
- makeCurrent(nextIndex);
- }
-
- function goToNext() {
- var nextIndex = 0;
-
- if (
- typeof currentIndex === 'number' &&
- currentIndex < missingCoverageElements.length - 1
- ) {
- nextIndex = currentIndex + 1;
- }
-
- makeCurrent(nextIndex);
- }
-
- return function jump(event) {
- if (
- document.getElementById('fileSearch') === document.activeElement &&
- document.activeElement != null
- ) {
- // if we're currently focused on the search input, we don't want to navigate
- return;
- }
-
- switch (event.which) {
- case 78: // n
- case 74: // j
- goToNext();
- break;
- case 66: // b
- case 75: // k
- case 80: // p
- goToPrevious();
- break;
- }
- };
-})();
-window.addEventListener('keydown', jumpToCode);
diff --git a/frontend/coverage/clover.xml b/frontend/coverage/clover.xml
deleted file mode 100644
index 8eba461..0000000
--- a/frontend/coverage/clover.xml
+++ /dev/null
@@ -1,385 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<coverage generated="1771133450877" clover="3.2.0">
- <project timestamp="1771133450877" name="All files">
- <metrics statements="331" coveredstatements="286" conditionals="228" coveredconditionals="167" methods="108" coveredmethods="88" elements="667" coveredelements="541" complexity="0" loc="331" ncloc="331" packages="2" files="14" classes="14"/>
- <package name="src">
- <metrics statements="45" coveredstatements="32" conditionals="35" coveredconditionals="23" methods="15" coveredmethods="9"/>
- <file name="App.css" path="/Users/adam/workspace/vibecode/neko/frontend/src/App.css">
- <metrics statements="0" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
- </file>
- <file name="App.tsx" path="/Users/adam/workspace/vibecode/neko/frontend/src/App.tsx">
- <metrics statements="34" coveredstatements="22" conditionals="25" coveredconditionals="15" methods="13" coveredmethods="7"/>
- <line num="9" count="2" type="stmt"/>
- <line num="10" count="2" type="stmt"/>
- <line num="12" count="2" type="stmt"/>
- <line num="13" count="1" type="stmt"/>
- <line num="15" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="16" count="1" type="stmt"/>
- <line num="18" count="0" type="stmt"/>
- <line num="21" count="0" type="stmt"/>
- <line num="24" count="2" type="cond" truecount="2" falsecount="0"/>
- <line num="25" count="1" type="stmt"/>
- <line num="28" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="29" count="0" type="stmt"/>
- <line num="32" count="1" type="stmt"/>
- <line num="47" count="1" type="stmt"/>
- <line num="49" count="1" type="stmt"/>
- <line num="50" count="1" type="stmt"/>
- <line num="51" count="0" type="cond" truecount="0" falsecount="2"/>
- <line num="52" count="0" type="stmt"/>
- <line num="54" count="0" type="stmt"/>
- <line num="57" count="1" type="stmt"/>
- <line num="58" count="1" type="stmt"/>
- <line num="61" count="1" type="stmt"/>
- <line num="69" count="0" type="stmt"/>
- <line num="78" count="0" type="stmt"/>
- <line num="103" count="2" type="cond" truecount="2" falsecount="0"/>
- <line num="104" count="2" type="cond" truecount="2" falsecount="0"/>
- <line num="106" count="2" type="stmt"/>
- <line num="107" count="0" type="stmt"/>
- <line num="108" count="0" type="stmt"/>
- <line num="111" count="2" type="stmt"/>
- <line num="112" count="0" type="stmt"/>
- <line num="113" count="0" type="stmt"/>
- <line num="116" count="2" type="cond" truecount="1" falsecount="1"/>
- <line num="118" count="2" type="stmt"/>
- </file>
- <file name="utils.ts" path="/Users/adam/workspace/vibecode/neko/frontend/src/utils.ts">
- <metrics statements="11" coveredstatements="10" conditionals="10" coveredconditionals="8" methods="2" coveredmethods="2"/>
- <line num="2" count="14" type="stmt"/>
- <line num="3" count="14" type="stmt"/>
- <line num="4" count="14" type="cond" truecount="1" falsecount="1"/>
- <line num="12" count="54" type="cond" truecount="2" falsecount="0"/>
- <line num="13" count="54" type="stmt"/>
- <line num="15" count="54" type="cond" truecount="2" falsecount="0"/>
- <line num="17" count="54" type="cond" truecount="2" falsecount="0"/>
- <line num="18" count="14" type="stmt"/>
- <line num="19" count="14" type="cond" truecount="1" falsecount="1"/>
- <line num="20" count="0" type="stmt"/>
- <line num="27" count="54" type="stmt"/>
- </file>
- </package>
- <package name="src.components">
- <metrics statements="286" coveredstatements="254" conditionals="193" coveredconditionals="144" methods="93" coveredmethods="79"/>
- <file name="FeedItem.css" path="/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.css">
- <metrics statements="0" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
- </file>
- <file name="FeedItem.tsx" path="/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.tsx">
- <metrics statements="31" coveredstatements="25" conditionals="23" coveredconditionals="20" methods="12" coveredmethods="10"/>
- <line num="12" count="56" type="stmt"/>
- <line num="13" count="56" type="stmt"/>
- <line num="15" count="56" type="stmt"/>
- <line num="16" count="22" type="stmt"/>
- <line num="19" count="56" type="stmt"/>
- <line num="20" count="1" type="stmt"/>
- <line num="23" count="56" type="stmt"/>
- <line num="24" count="1" type="stmt"/>
- <line num="26" count="1" type="stmt"/>
- <line num="27" count="1" type="stmt"/>
- <line num="29" count="1" type="stmt"/>
- <line num="41" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="42" count="0" type="stmt"/>
- <line num="44" count="1" type="stmt"/>
- <line num="49" count="1" type="stmt"/>
- <line num="52" count="0" type="stmt"/>
- <line num="54" count="0" type="stmt"/>
- <line num="55" count="0" type="stmt"/>
- <line num="59" count="56" type="stmt"/>
- <line num="60" count="1" type="stmt"/>
- <line num="61" count="1" type="stmt"/>
- <line num="62" count="1" type="stmt"/>
- <line num="64" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="65" count="1" type="stmt"/>
- <line num="68" count="1" type="stmt"/>
- <line num="69" count="1" type="stmt"/>
- <line num="72" count="0" type="stmt"/>
- <line num="73" count="0" type="stmt"/>
- <line num="77" count="56" type="stmt"/>
- <line num="85" count="1" type="stmt"/>
- <line num="86" count="1" type="stmt"/>
- </file>
- <file name="FeedItems.css" path="/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.css">
- <metrics statements="0" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
- </file>
- <file name="FeedItems.tsx" path="/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.tsx">
- <metrics statements="119" coveredstatements="106" conditionals="84" coveredconditionals="64" methods="31" coveredmethods="27"/>
- <line num="9" count="36" type="stmt"/>
- <line num="10" count="36" type="stmt"/>
- <line num="11" count="36" type="cond" truecount="2" falsecount="0"/>
- <line num="13" count="36" type="stmt"/>
- <line num="14" count="36" type="stmt"/>
- <line num="15" count="36" type="stmt"/>
- <line num="16" count="36" type="stmt"/>
- <line num="17" count="36" type="stmt"/>
- <line num="18" count="36" type="stmt"/>
- <line num="20" count="36" type="stmt"/>
- <line num="21" count="11" type="cond" truecount="2" falsecount="0"/>
- <line num="22" count="3" type="stmt"/>
- <line num="24" count="8" type="stmt"/>
- <line num="25" count="8" type="stmt"/>
- <line num="27" count="11" type="stmt"/>
- <line num="29" count="11" type="stmt"/>
- <line num="30" count="11" type="stmt"/>
- <line num="32" count="11" type="cond" truecount="2" falsecount="0"/>
- <line num="33" count="2" type="stmt"/>
- <line num="34" count="9" type="cond" truecount="2" falsecount="0"/>
- <line num="35" count="1" type="stmt"/>
- <line num="38" count="11" type="cond" truecount="2" falsecount="0"/>
- <line num="39" count="3" type="stmt"/>
- <line num="43" count="11" type="stmt"/>
- <line num="44" count="11" type="cond" truecount="1" falsecount="1"/>
- <line num="45" count="0" type="stmt"/>
- <line num="48" count="11" type="cond" truecount="1" falsecount="1"/>
- <line num="49" count="0" type="stmt"/>
- <line num="50" count="11" type="cond" truecount="1" falsecount="1"/>
- <line num="51" count="0" type="stmt"/>
- <line num="52" count="0" type="stmt"/>
- <line num="55" count="11" type="cond" truecount="1" falsecount="1"/>
- <line num="56" count="11" type="stmt"/>
- <line num="60" count="11" type="stmt"/>
- <line num="61" count="11" type="cond" truecount="1" falsecount="1"/>
- <line num="62" count="11" type="stmt"/>
- <line num="65" count="11" type="stmt"/>
- <line num="67" count="10" type="cond" truecount="1" falsecount="1"/>
- <line num="68" count="0" type="stmt"/>
- <line num="70" count="10" type="stmt"/>
- <line num="73" count="9" type="cond" truecount="2" falsecount="0"/>
- <line num="74" count="3" type="stmt"/>
- <line num="76" count="6" type="stmt"/>
- <line num="78" count="9" type="stmt"/>
- <line num="79" count="9" type="stmt"/>
- <line num="80" count="9" type="stmt"/>
- <line num="83" count="1" type="stmt"/>
- <line num="84" count="1" type="stmt"/>
- <line num="85" count="1" type="stmt"/>
- <line num="89" count="36" type="stmt"/>
- <line num="90" count="8" type="stmt"/>
- <line num="91" count="8" type="stmt"/>
- <line num="96" count="36" type="stmt"/>
- <line num="97" count="5" type="stmt"/>
- <line num="98" count="5" type="cond" truecount="1" falsecount="1"/>
- <line num="99" count="5" type="stmt"/>
- <line num="103" count="36" type="stmt"/>
- <line num="104" count="2" type="stmt"/>
- <line num="106" count="3" type="cond" truecount="2" falsecount="0"/>
- <line num="108" count="2" type="stmt"/>
- <line num="112" count="0" type="stmt"/>
- <line num="115" count="36" type="stmt"/>
- <line num="116" count="1" type="stmt"/>
- <line num="118" count="2" type="cond" truecount="2" falsecount="0"/>
- <line num="120" count="1" type="stmt"/>
- <line num="124" count="0" type="stmt"/>
- <line num="127" count="36" type="stmt"/>
- <line num="128" count="31" type="stmt"/>
- <line num="129" count="6" type="cond" truecount="1" falsecount="1"/>
- <line num="131" count="6" type="cond" truecount="2" falsecount="0"/>
- <line num="132" count="5" type="stmt"/>
- <line num="133" count="5" type="stmt"/>
- <line num="134" count="5" type="cond" truecount="1" falsecount="1"/>
- <line num="135" count="5" type="stmt"/>
- <line num="136" count="5" type="cond" truecount="2" falsecount="0"/>
- <line num="137" count="1" type="stmt"/>
- <line num="139" count="5" type="stmt"/>
- <line num="144" count="5" type="cond" truecount="5" falsecount="0"/>
- <line num="145" count="2" type="stmt"/>
- <line num="148" count="5" type="stmt"/>
- <line num="150" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="151" count="0" type="stmt"/>
- <line num="152" count="0" type="stmt"/>
- <line num="153" count="0" type="cond" truecount="0" falsecount="2"/>
- <line num="154" count="0" type="stmt"/>
- <line num="156" count="0" type="stmt"/>
- <line num="158" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="159" count="1" type="stmt"/>
- <line num="160" count="1" type="cond" truecount="3" falsecount="1"/>
- <line num="161" count="1" type="stmt"/>
- <line num="163" count="1" type="stmt"/>
- <line num="168" count="31" type="stmt"/>
- <line num="169" count="31" type="stmt"/>
- <line num="175" count="36" type="stmt"/>
- <line num="177" count="31" type="stmt"/>
- <line num="179" count="1" type="stmt"/>
- <line num="181" count="1" type="cond" truecount="3" falsecount="1"/>
- <line num="182" count="1" type="stmt"/>
- <line num="183" count="1" type="cond" truecount="4" falsecount="1"/>
- <line num="184" count="1" type="stmt"/>
- <line num="185" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="186" count="1" type="stmt"/>
- <line num="196" count="31" type="stmt"/>
- <line num="198" count="1" type="stmt"/>
- <line num="199" count="1" type="cond" truecount="5" falsecount="1"/>
- <line num="200" count="1" type="stmt"/>
- <line num="207" count="31" type="stmt"/>
- <line num="208" count="31" type="stmt"/>
- <line num="209" count="31" type="cond" truecount="1" falsecount="1"/>
- <line num="212" count="31" type="stmt"/>
- <line num="213" count="31" type="cond" truecount="2" falsecount="0"/>
- <line num="215" count="31" type="stmt"/>
- <line num="216" count="31" type="stmt"/>
- <line num="217" count="31" type="stmt"/>
- <line num="222" count="36" type="cond" truecount="2" falsecount="0"/>
- <line num="223" count="21" type="cond" truecount="2" falsecount="0"/>
- <line num="225" count="20" type="stmt"/>
- <line num="232" count="44" type="stmt"/>
- <line num="237" count="0" type="stmt"/>
- </file>
- <file name="FeedList.css" path="/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.css">
- <metrics statements="0" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
- </file>
- <file name="FeedList.tsx" path="/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.tsx">
- <metrics statements="50" coveredstatements="45" conditionals="50" coveredconditionals="35" methods="19" coveredmethods="15"/>
- <line num="19" count="22" type="stmt"/>
- <line num="20" count="22" type="stmt"/>
- <line num="21" count="22" type="stmt"/>
- <line num="22" count="22" type="stmt"/>
- <line num="23" count="22" type="stmt"/>
- <line num="24" count="22" type="stmt"/>
- <line num="25" count="22" type="stmt"/>
- <line num="26" count="22" type="stmt"/>
- <line num="27" count="22" type="stmt"/>
- <line num="28" count="22" type="stmt"/>
- <line num="29" count="22" type="stmt"/>
- <line num="31" count="22" type="cond" truecount="3" falsecount="0"/>
- <line num="33" count="22" type="stmt"/>
- <line num="34" count="11" type="stmt"/>
- <line num="35" count="11" type="cond" truecount="1" falsecount="1"/>
- <line num="36" count="0" type="stmt"/>
- <line num="41" count="22" type="cond" truecount="2" falsecount="0"/>
- <line num="44" count="22" type="stmt"/>
- <line num="45" count="1" type="stmt"/>
- <line num="46" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="47" count="1" type="stmt"/>
- <line num="51" count="22" type="stmt"/>
- <line num="52" count="2" type="stmt"/>
- <line num="55" count="22" type="stmt"/>
- <line num="56" count="0" type="stmt"/>
- <line num="59" count="22" type="stmt"/>
- <line num="60" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="61" count="1" type="stmt"/>
- <line num="65" count="22" type="stmt"/>
- <line num="66" count="9" type="stmt"/>
- <line num="68" count="7" type="cond" truecount="1" falsecount="1"/>
- <line num="69" count="7" type="stmt"/>
- <line num="72" count="7" type="cond" truecount="1" falsecount="1"/>
- <line num="73" count="7" type="stmt"/>
- <line num="77" count="7" type="stmt"/>
- <line num="78" count="7" type="stmt"/>
- <line num="79" count="7" type="stmt"/>
- <line num="82" count="1" type="stmt"/>
- <line num="83" count="1" type="stmt"/>
- <line num="87" count="22" type="cond" truecount="2" falsecount="0"/>
- <line num="88" count="13" type="cond" truecount="2" falsecount="0"/>
- <line num="90" count="12" type="stmt"/>
- <line num="91" count="2" type="stmt"/>
- <line num="94" count="12" type="stmt"/>
- <line num="96" count="0" type="stmt"/>
- <line num="106" count="1" type="stmt"/>
- <line num="139" count="4" type="stmt"/>
- <line num="163" count="2" type="stmt"/>
- <line num="195" count="0" type="stmt"/>
- <line num="202" count="0" type="stmt"/>
- </file>
- <file name="FeedListVariants.css" path="/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedListVariants.css">
- <metrics statements="0" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
- </file>
- <file name="Login.css" path="/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.css">
- <metrics statements="0" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
- </file>
- <file name="Login.tsx" path="/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.tsx">
- <metrics statements="20" coveredstatements="20" conditionals="6" coveredconditionals="5" methods="4" coveredmethods="4"/>
- <line num="8" count="17" type="stmt"/>
- <line num="9" count="17" type="stmt"/>
- <line num="10" count="17" type="stmt"/>
- <line num="11" count="17" type="stmt"/>
- <line num="13" count="17" type="stmt"/>
- <line num="14" count="3" type="stmt"/>
- <line num="15" count="3" type="stmt"/>
- <line num="17" count="3" type="stmt"/>
- <line num="19" count="3" type="stmt"/>
- <line num="20" count="3" type="stmt"/>
- <line num="21" count="3" type="stmt"/>
- <line num="23" count="3" type="stmt"/>
- <line num="28" count="2" type="cond" truecount="2" falsecount="0"/>
- <line num="29" count="1" type="stmt"/>
- <line num="31" count="1" type="stmt"/>
- <line num="32" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="35" count="1" type="stmt"/>
- <line num="39" count="17" type="stmt"/>
- <line num="49" count="3" type="stmt"/>
- <line num="58" count="3" type="stmt"/>
- </file>
- <file name="Settings.css" path="/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.css">
- <metrics statements="0" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
- </file>
- <file name="Settings.tsx" path="/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.tsx">
- <metrics statements="66" coveredstatements="58" conditionals="30" coveredconditionals="20" methods="27" coveredmethods="23"/>
- <line num="12" count="34" type="stmt"/>
- <line num="14" count="34" type="stmt"/>
- <line num="15" count="34" type="stmt"/>
- <line num="16" count="34" type="stmt"/>
- <line num="18" count="34" type="stmt"/>
- <line num="21" count="34" type="stmt"/>
- <line num="22" count="9" type="stmt"/>
- <line num="23" count="9" type="stmt"/>
- <line num="25" count="9" type="cond" truecount="1" falsecount="1"/>
- <line num="26" count="9" type="stmt"/>
- <line num="29" count="9" type="stmt"/>
- <line num="30" count="9" type="stmt"/>
- <line num="33" count="0" type="stmt"/>
- <line num="34" count="0" type="stmt"/>
- <line num="38" count="34" type="stmt"/>
- <line num="40" count="7" type="stmt"/>
- <line num="44" count="34" type="stmt"/>
- <line num="45" count="2" type="stmt"/>
- <line num="46" count="2" type="cond" truecount="1" falsecount="1"/>
- <line num="48" count="2" type="stmt"/>
- <line num="49" count="2" type="stmt"/>
- <line num="55" count="2" type="cond" truecount="2" falsecount="0"/>
- <line num="56" count="1" type="stmt"/>
- <line num="59" count="1" type="stmt"/>
- <line num="60" count="1" type="stmt"/>
- <line num="63" count="1" type="stmt"/>
- <line num="64" count="1" type="stmt"/>
- <line num="68" count="34" type="stmt"/>
- <line num="69" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="71" count="1" type="stmt"/>
- <line num="72" count="1" type="stmt"/>
- <line num="76" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="77" count="1" type="stmt"/>
- <line num="78" count="1" type="stmt"/>
- <line num="81" count="0" type="stmt"/>
- <line num="82" count="0" type="stmt"/>
- <line num="86" count="34" type="stmt"/>
- <line num="87" count="1" type="stmt"/>
- <line num="88" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="90" count="1" type="stmt"/>
- <line num="91" count="1" type="stmt"/>
- <line num="92" count="1" type="stmt"/>
- <line num="93" count="1" type="stmt"/>
- <line num="95" count="1" type="stmt"/>
- <line num="100" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="101" count="1" type="stmt"/>
- <line num="104" count="1" type="stmt"/>
- <line num="105" count="1" type="stmt"/>
- <line num="106" count="1" type="stmt"/>
- <line num="109" count="0" type="stmt"/>
- <line num="110" count="0" type="stmt"/>
- <line num="114" count="34" type="stmt"/>
- <line num="115" count="1" type="stmt"/>
- <line num="116" count="1" type="stmt"/>
- <line num="120" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="121" count="1" type="stmt"/>
- <line num="124" count="1" type="stmt"/>
- <line num="125" count="1" type="stmt"/>
- <line num="128" count="0" type="stmt"/>
- <line num="129" count="0" type="stmt"/>
- <line num="133" count="34" type="stmt"/>
- <line num="145" count="1" type="stmt"/>
- <line num="163" count="2" type="stmt"/>
- <line num="183" count="1" type="cond" truecount="1" falsecount="1"/>
- <line num="217" count="6" type="stmt"/>
- <line num="223" count="1" type="stmt"/>
- </file>
- </package>
- </project>
-</coverage>
diff --git a/frontend/coverage/coverage-final.json b/frontend/coverage/coverage-final.json
deleted file mode 100644
index b8f97aa..0000000
--- a/frontend/coverage/coverage-final.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{"/Users/adam/workspace/vibecode/neko/frontend/src/App.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/App.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/App.tsx": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/App.tsx","statementMap":{"0":{"start":{"line":9,"column":22},"end":{"line":9,"column":null}},"1":{"start":{"line":10,"column":8},"end":{"line":10,"column":null}},"2":{"start":{"line":12,"column":2},"end":{"line":22,"column":null}},"3":{"start":{"line":13,"column":4},"end":{"line":21,"column":null}},"4":{"start":{"line":15,"column":8},"end":{"line":19,"column":null}},"5":{"start":{"line":16,"column":10},"end":{"line":16,"column":null}},"6":{"start":{"line":18,"column":10},"end":{"line":18,"column":null}},"7":{"start":{"line":21,"column":19},"end":{"line":21,"column":33}},"8":{"start":{"line":24,"column":2},"end":{"line":26,"column":null}},"9":{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},"10":{"start":{"line":28,"column":2},"end":{"line":30,"column":null}},"11":{"start":{"line":29,"column":4},"end":{"line":29,"column":null}},"12":{"start":{"line":32,"column":2},"end":{"line":32,"column":null}},"13":{"start":{"line":47,"column":42},"end":{"line":47,"column":null}},"14":{"start":{"line":49,"column":2},"end":{"line":59,"column":null}},"15":{"start":{"line":50,"column":25},"end":{"line":56,"column":null}},"16":{"start":{"line":51,"column":6},"end":{"line":55,"column":null}},"17":{"start":{"line":52,"column":8},"end":{"line":52,"column":null}},"18":{"start":{"line":54,"column":8},"end":{"line":54,"column":null}},"19":{"start":{"line":57,"column":4},"end":{"line":57,"column":null}},"20":{"start":{"line":58,"column":4},"end":{"line":58,"column":null}},"21":{"start":{"line":58,"column":17},"end":{"line":58,"column":null}},"22":{"start":{"line":61,"column":2},"end":{"line":98,"column":null}},"23":{"start":{"line":69,"column":27},"end":{"line":69,"column":null}},"24":{"start":{"line":78,"column":27},"end":{"line":78,"column":null}},"25":{"start":{"line":103,"column":24},"end":{"line":103,"column":null}},"26":{"start":{"line":104,"column":32},"end":{"line":104,"column":null}},"27":{"start":{"line":106,"column":25},"end":{"line":109,"column":null}},"28":{"start":{"line":107,"column":4},"end":{"line":107,"column":null}},"29":{"start":{"line":108,"column":4},"end":{"line":108,"column":null}},"30":{"start":{"line":111,"column":29},"end":{"line":114,"column":null}},"31":{"start":{"line":112,"column":4},"end":{"line":112,"column":null}},"32":{"start":{"line":113,"column":4},"end":{"line":113,"column":null}},"33":{"start":{"line":116,"column":19},"end":{"line":116,"column":null}},"34":{"start":{"line":118,"column":2},"end":{"line":136,"column":null}}},"fnMap":{"0":{"name":"RequireAuth","decl":{"start":{"line":8,"column":9},"end":{"line":8,"column":21}},"loc":{"start":{"line":8,"column":69},"end":{"line":33,"column":null}},"line":8},"1":{"name":"(anonymous_1)","decl":{"start":{"line":12,"column":12},"end":{"line":12,"column":18}},"loc":{"start":{"line":12,"column":18},"end":{"line":22,"column":5}},"line":12},"2":{"name":"(anonymous_2)","decl":{"start":{"line":14,"column":12},"end":{"line":14,"column":13}},"loc":{"start":{"line":14,"column":21},"end":{"line":20,"column":7}},"line":14},"3":{"name":"(anonymous_3)","decl":{"start":{"line":21,"column":13},"end":{"line":21,"column":19}},"loc":{"start":{"line":21,"column":19},"end":{"line":21,"column":33}},"line":21},"4":{"name":"Dashboard","decl":{"start":{"line":46,"column":9},"end":{"line":46,"column":19}},"loc":{"start":{"line":46,"column":81},"end":{"line":100,"column":null}},"line":46},"5":{"name":"(anonymous_5)","decl":{"start":{"line":49,"column":12},"end":{"line":49,"column":18}},"loc":{"start":{"line":49,"column":18},"end":{"line":59,"column":5}},"line":49},"6":{"name":"(anonymous_6)","decl":{"start":{"line":50,"column":25},"end":{"line":50,"column":31}},"loc":{"start":{"line":50,"column":31},"end":{"line":56,"column":null}},"line":50},"7":{"name":"(anonymous_7)","decl":{"start":{"line":58,"column":11},"end":{"line":58,"column":17}},"loc":{"start":{"line":58,"column":17},"end":{"line":58,"column":null}},"line":58},"8":{"name":"(anonymous_8)","decl":{"start":{"line":69,"column":21},"end":{"line":69,"column":27}},"loc":{"start":{"line":69,"column":27},"end":{"line":69,"column":null}},"line":69},"9":{"name":"(anonymous_9)","decl":{"start":{"line":78,"column":21},"end":{"line":78,"column":27}},"loc":{"start":{"line":78,"column":27},"end":{"line":78,"column":null}},"line":78},"10":{"name":"App","decl":{"start":{"line":102,"column":9},"end":{"line":102,"column":15}},"loc":{"start":{"line":102,"column":15},"end":{"line":138,"column":null}},"line":102},"11":{"name":"(anonymous_11)","decl":{"start":{"line":106,"column":25},"end":{"line":106,"column":26}},"loc":{"start":{"line":106,"column":47},"end":{"line":109,"column":null}},"line":106},"12":{"name":"(anonymous_12)","decl":{"start":{"line":111,"column":29},"end":{"line":111,"column":30}},"loc":{"start":{"line":111,"column":55},"end":{"line":114,"column":null}},"line":111}},"branchMap":{"0":{"loc":{"start":{"line":15,"column":8},"end":{"line":19,"column":null}},"type":"if","locations":[{"start":{"line":15,"column":8},"end":{"line":19,"column":null}},{"start":{"line":17,"column":15},"end":{"line":19,"column":null}}],"line":15},"1":{"loc":{"start":{"line":24,"column":2},"end":{"line":26,"column":null}},"type":"if","locations":[{"start":{"line":24,"column":2},"end":{"line":26,"column":null}},{"start":{},"end":{}}],"line":24},"2":{"loc":{"start":{"line":28,"column":2},"end":{"line":30,"column":null}},"type":"if","locations":[{"start":{"line":28,"column":2},"end":{"line":30,"column":null}},{"start":{},"end":{}}],"line":28},"3":{"loc":{"start":{"line":51,"column":6},"end":{"line":55,"column":null}},"type":"if","locations":[{"start":{"line":51,"column":6},"end":{"line":55,"column":null}},{"start":{"line":53,"column":13},"end":{"line":55,"column":null}}],"line":51},"4":{"loc":{"start":{"line":63,"column":30},"end":{"line":63,"column":83}},"type":"cond-expr","locations":[{"start":{"line":63,"column":47},"end":{"line":63,"column":67}},{"start":{"line":63,"column":67},"end":{"line":63,"column":83}}],"line":63},"5":{"loc":{"start":{"line":66,"column":10},"end":{"line":73,"column":null}},"type":"binary-expr","locations":[{"start":{"line":66,"column":10},"end":{"line":66,"column":29}},{"start":{"line":66,"column":29},"end":{"line":66,"column":null}},{"start":{"line":67,"column":10},"end":{"line":73,"column":null}}],"line":66},"6":{"loc":{"start":{"line":70,"column":19},"end":{"line":70,"column":null}},"type":"cond-expr","locations":[{"start":{"line":70,"column":36},"end":{"line":70,"column":53}},{"start":{"line":70,"column":53},"end":{"line":70,"column":null}}],"line":70},"7":{"loc":{"start":{"line":75,"column":9},"end":{"line":79,"column":null}},"type":"binary-expr","locations":[{"start":{"line":75,"column":9},"end":{"line":75,"column":null}},{"start":{"line":76,"column":10},"end":{"line":79,"column":null}}],"line":75},"8":{"loc":{"start":{"line":81,"column":47},"end":{"line":81,"column":77}},"type":"cond-expr","locations":[{"start":{"line":81,"column":64},"end":{"line":81,"column":69}},{"start":{"line":81,"column":69},"end":{"line":81,"column":77}}],"line":81},"9":{"loc":{"start":{"line":103,"column":37},"end":{"line":103,"column":82}},"type":"binary-expr","locations":[{"start":{"line":103,"column":37},"end":{"line":103,"column":75}},{"start":{"line":103,"column":75},"end":{"line":103,"column":82}}],"line":103},"10":{"loc":{"start":{"line":104,"column":45},"end":{"line":104,"column":97}},"type":"binary-expr","locations":[{"start":{"line":104,"column":45},"end":{"line":104,"column":88}},{"start":{"line":104,"column":88},"end":{"line":104,"column":97}}],"line":104},"11":{"loc":{"start":{"line":116,"column":19},"end":{"line":116,"column":null}},"type":"cond-expr","locations":[{"start":{"line":116,"column":64},"end":{"line":116,"column":72}},{"start":{"line":116,"column":72},"end":{"line":116,"column":null}}],"line":116}},"s":{"0":2,"1":2,"2":2,"3":1,"4":1,"5":1,"6":0,"7":0,"8":2,"9":1,"10":1,"11":0,"12":1,"13":1,"14":1,"15":1,"16":0,"17":0,"18":0,"19":1,"20":1,"21":1,"22":1,"23":0,"24":0,"25":2,"26":2,"27":2,"28":0,"29":0,"30":2,"31":0,"32":0,"33":2,"34":2},"f":{"0":2,"1":1,"2":1,"3":0,"4":1,"5":1,"6":0,"7":1,"8":0,"9":0,"10":2,"11":0,"12":0},"b":{"0":[1,0],"1":[1,1],"2":[0,1],"3":[0,0],"4":[1,0],"5":[1,1,0],"6":[0,0],"7":[1,1],"8":[1,0],"9":[2,2],"10":[2,2],"11":[2,0]},"meta":{"lastBranch":12,"lastFunction":13,"lastStatement":35,"seen":{"f:8:9:8:21":0,"s:9:22:9:Infinity":0,"s:10:8:10:Infinity":1,"s:12:2:22:Infinity":2,"f:12:12:12:18":1,"s:13:4:21:Infinity":3,"f:14:12:14:13":2,"b:15:8:19:Infinity:17:15:19:Infinity":0,"s:15:8:19:Infinity":4,"s:16:10:16:Infinity":5,"s:18:10:18:Infinity":6,"f:21:13:21:19":3,"s:21:19:21:33":7,"b:24:2:26:Infinity:undefined:undefined:undefined:undefined":1,"s:24:2:26:Infinity":8,"s:25:4:25:Infinity":9,"b:28:2:30:Infinity:undefined:undefined:undefined:undefined":2,"s:28:2:30:Infinity":10,"s:29:4:29:Infinity":11,"s:32:2:32:Infinity":12,"f:46:9:46:19":4,"s:47:42:47:Infinity":13,"s:49:2:59:Infinity":14,"f:49:12:49:18":5,"s:50:25:56:Infinity":15,"f:50:25:50:31":6,"b:51:6:55:Infinity:53:13:55:Infinity":3,"s:51:6:55:Infinity":16,"s:52:8:52:Infinity":17,"s:54:8:54:Infinity":18,"s:57:4:57:Infinity":19,"s:58:4:58:Infinity":20,"f:58:11:58:17":7,"s:58:17:58:Infinity":21,"s:61:2:98:Infinity":22,"b:63:47:63:67:63:67:63:83":4,"b:66:10:66:29:66:29:66:Infinity:67:10:73:Infinity":5,"f:69:21:69:27":8,"s:69:27:69:Infinity":23,"b:70:36:70:53:70:53:70:Infinity":6,"b:75:9:75:Infinity:76:10:79:Infinity":7,"f:78:21:78:27":9,"s:78:27:78:Infinity":24,"b:81:64:81:69:81:69:81:77":8,"f:102:9:102:15":10,"s:103:24:103:Infinity":25,"b:103:37:103:75:103:75:103:82":9,"s:104:32:104:Infinity":26,"b:104:45:104:88:104:88:104:97":10,"s:106:25:109:Infinity":27,"f:106:25:106:26":11,"s:107:4:107:Infinity":28,"s:108:4:108:Infinity":29,"s:111:29:114:Infinity":30,"f:111:29:111:30":12,"s:112:4:112:Infinity":31,"s:113:4:113:Infinity":32,"s:116:19:116:Infinity":33,"b:116:64:116:72:116:72:116:Infinity":11,"s:118:2:136:Infinity":34}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/utils.ts": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/utils.ts","statementMap":{"0":{"start":{"line":2,"column":18},"end":{"line":2,"column":null}},"1":{"start":{"line":3,"column":18},"end":{"line":3,"column":null}},"2":{"start":{"line":4,"column":4},"end":{"line":4,"column":null}},"3":{"start":{"line":4,"column":28},"end":{"line":4,"column":null}},"4":{"start":{"line":12,"column":19},"end":{"line":12,"column":null}},"5":{"start":{"line":13,"column":28},"end":{"line":13,"column":null}},"6":{"start":{"line":15,"column":20},"end":{"line":15,"column":null}},"7":{"start":{"line":17,"column":4},"end":{"line":22,"column":null}},"8":{"start":{"line":18,"column":22},"end":{"line":18,"column":null}},"9":{"start":{"line":19,"column":8},"end":{"line":21,"column":null}},"10":{"start":{"line":20,"column":12},"end":{"line":20,"column":null}},"11":{"start":{"line":27,"column":4},"end":{"line":31,"column":null}}},"fnMap":{"0":{"name":"getCookie","decl":{"start":{"line":1,"column":16},"end":{"line":1,"column":26}},"loc":{"start":{"line":1,"column":60},"end":{"line":5,"column":null}},"line":1},"1":{"name":"apiFetch","decl":{"start":{"line":11,"column":22},"end":{"line":11,"column":31}},"loc":{"start":{"line":11,"column":96},"end":{"line":32,"column":null}},"line":11}},"branchMap":{"0":{"loc":{"start":{"line":4,"column":4},"end":{"line":4,"column":null}},"type":"if","locations":[{"start":{"line":4,"column":4},"end":{"line":4,"column":null}},{"start":{},"end":{}}],"line":4},"1":{"loc":{"start":{"line":12,"column":19},"end":{"line":12,"column":null}},"type":"binary-expr","locations":[{"start":{"line":12,"column":19},"end":{"line":12,"column":50}},{"start":{"line":12,"column":50},"end":{"line":12,"column":null}}],"line":12},"2":{"loc":{"start":{"line":15,"column":32},"end":{"line":15,"column":51}},"type":"binary-expr","locations":[{"start":{"line":15,"column":32},"end":{"line":15,"column":49}},{"start":{"line":15,"column":49},"end":{"line":15,"column":51}}],"line":15},"3":{"loc":{"start":{"line":17,"column":4},"end":{"line":22,"column":null}},"type":"if","locations":[{"start":{"line":17,"column":4},"end":{"line":22,"column":null}},{"start":{},"end":{}}],"line":17},"4":{"loc":{"start":{"line":19,"column":8},"end":{"line":21,"column":null}},"type":"if","locations":[{"start":{"line":19,"column":8},"end":{"line":21,"column":null}},{"start":{},"end":{}}],"line":19}},"s":{"0":14,"1":14,"2":14,"3":0,"4":54,"5":54,"6":54,"7":54,"8":14,"9":14,"10":0,"11":54},"f":{"0":14,"1":54},"b":{"0":[0,14],"1":[54,40],"2":[54,48],"3":[14,40],"4":[0,14]},"meta":{"lastBranch":5,"lastFunction":2,"lastStatement":12,"seen":{"f:1:16:1:26":0,"s:2:18:2:Infinity":0,"s:3:18:3:Infinity":1,"b:4:4:4:Infinity:undefined:undefined:undefined:undefined":0,"s:4:4:4:Infinity":2,"s:4:28:4:Infinity":3,"f:11:22:11:31":1,"s:12:19:12:Infinity":4,"b:12:19:12:50:12:50:12:Infinity":1,"s:13:28:13:Infinity":5,"s:15:20:15:Infinity":6,"b:15:32:15:49:15:49:15:51":2,"b:17:4:22:Infinity:undefined:undefined:undefined:undefined":3,"s:17:4:22:Infinity":7,"s:18:22:18:Infinity":8,"b:19:8:21:Infinity:undefined:undefined:undefined:undefined":4,"s:19:8:21:Infinity":9,"s:20:12:20:Infinity":10,"s:27:4:31:Infinity":11}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.tsx": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.tsx","statementMap":{"0":{"start":{"line":12,"column":22},"end":{"line":12,"column":null}},"1":{"start":{"line":13,"column":28},"end":{"line":13,"column":null}},"2":{"start":{"line":15,"column":2},"end":{"line":17,"column":null}},"3":{"start":{"line":16,"column":4},"end":{"line":16,"column":null}},"4":{"start":{"line":19,"column":21},"end":{"line":21,"column":null}},"5":{"start":{"line":20,"column":4},"end":{"line":20,"column":null}},"6":{"start":{"line":23,"column":21},"end":{"line":57,"column":null}},"7":{"start":{"line":24,"column":4},"end":{"line":24,"column":null}},"8":{"start":{"line":26,"column":25},"end":{"line":26,"column":null}},"9":{"start":{"line":27,"column":4},"end":{"line":27,"column":null}},"10":{"start":{"line":29,"column":4},"end":{"line":56,"column":null}},"11":{"start":{"line":41,"column":8},"end":{"line":43,"column":null}},"12":{"start":{"line":42,"column":10},"end":{"line":42,"column":null}},"13":{"start":{"line":44,"column":8},"end":{"line":44,"column":null}},"14":{"start":{"line":49,"column":8},"end":{"line":49,"column":null}},"15":{"start":{"line":52,"column":8},"end":{"line":52,"column":null}},"16":{"start":{"line":54,"column":8},"end":{"line":54,"column":null}},"17":{"start":{"line":55,"column":8},"end":{"line":55,"column":null}},"18":{"start":{"line":59,"column":26},"end":{"line":75,"column":null}},"19":{"start":{"line":60,"column":4},"end":{"line":60,"column":null}},"20":{"start":{"line":61,"column":4},"end":{"line":61,"column":null}},"21":{"start":{"line":62,"column":4},"end":{"line":74,"column":null}},"22":{"start":{"line":64,"column":8},"end":{"line":64,"column":null}},"23":{"start":{"line":64,"column":21},"end":{"line":64,"column":null}},"24":{"start":{"line":65,"column":8},"end":{"line":65,"column":null}},"25":{"start":{"line":68,"column":8},"end":{"line":68,"column":null}},"26":{"start":{"line":69,"column":8},"end":{"line":69,"column":null}},"27":{"start":{"line":72,"column":8},"end":{"line":72,"column":null}},"28":{"start":{"line":73,"column":8},"end":{"line":73,"column":null}},"29":{"start":{"line":77,"column":2},"end":{"line":113,"column":null}},"30":{"start":{"line":85,"column":12},"end":{"line":85,"column":null}},"31":{"start":{"line":86,"column":12},"end":{"line":86,"column":null}}},"fnMap":{"0":{"name":"FeedItem","decl":{"start":{"line":11,"column":24},"end":{"line":11,"column":33}},"loc":{"start":{"line":11,"column":71},"end":{"line":115,"column":null}},"line":11},"1":{"name":"(anonymous_1)","decl":{"start":{"line":15,"column":12},"end":{"line":15,"column":18}},"loc":{"start":{"line":15,"column":18},"end":{"line":17,"column":5}},"line":15},"2":{"name":"(anonymous_2)","decl":{"start":{"line":19,"column":21},"end":{"line":19,"column":27}},"loc":{"start":{"line":19,"column":27},"end":{"line":21,"column":null}},"line":19},"3":{"name":"(anonymous_3)","decl":{"start":{"line":23,"column":21},"end":{"line":23,"column":22}},"loc":{"start":{"line":23,"column":40},"end":{"line":57,"column":null}},"line":23},"4":{"name":"(anonymous_4)","decl":{"start":{"line":40,"column":12},"end":{"line":40,"column":13}},"loc":{"start":{"line":40,"column":21},"end":{"line":45,"column":7}},"line":40},"5":{"name":"(anonymous_5)","decl":{"start":{"line":46,"column":12},"end":{"line":46,"column":18}},"loc":{"start":{"line":46,"column":18},"end":{"line":50,"column":7}},"line":46},"6":{"name":"(anonymous_6)","decl":{"start":{"line":51,"column":13},"end":{"line":51,"column":14}},"loc":{"start":{"line":51,"column":22},"end":{"line":56,"column":7}},"line":51},"7":{"name":"(anonymous_7)","decl":{"start":{"line":59,"column":26},"end":{"line":59,"column":27}},"loc":{"start":{"line":59,"column":51},"end":{"line":75,"column":null}},"line":59},"8":{"name":"(anonymous_8)","decl":{"start":{"line":63,"column":12},"end":{"line":63,"column":13}},"loc":{"start":{"line":63,"column":21},"end":{"line":66,"column":7}},"line":63},"9":{"name":"(anonymous_9)","decl":{"start":{"line":67,"column":12},"end":{"line":67,"column":13}},"loc":{"start":{"line":67,"column":22},"end":{"line":70,"column":7}},"line":67},"10":{"name":"(anonymous_10)","decl":{"start":{"line":71,"column":13},"end":{"line":71,"column":14}},"loc":{"start":{"line":71,"column":22},"end":{"line":74,"column":7}},"line":71},"11":{"name":"(anonymous_11)","decl":{"start":{"line":84,"column":19},"end":{"line":84,"column":20}},"loc":{"start":{"line":84,"column":26},"end":{"line":87,"column":null}},"line":84}},"branchMap":{"0":{"loc":{"start":{"line":41,"column":8},"end":{"line":43,"column":null}},"type":"if","locations":[{"start":{"line":41,"column":8},"end":{"line":43,"column":null}},{"start":{},"end":{}}],"line":41},"1":{"loc":{"start":{"line":64,"column":8},"end":{"line":64,"column":null}},"type":"if","locations":[{"start":{"line":64,"column":8},"end":{"line":64,"column":null}},{"start":{},"end":{}}],"line":64},"2":{"loc":{"start":{"line":78,"column":32},"end":{"line":78,"column":61}},"type":"cond-expr","locations":[{"start":{"line":78,"column":44},"end":{"line":78,"column":53}},{"start":{"line":78,"column":53},"end":{"line":78,"column":61}}],"line":78},"3":{"loc":{"start":{"line":78,"column":65},"end":{"line":78,"column":89}},"type":"cond-expr","locations":[{"start":{"line":78,"column":75},"end":{"line":78,"column":87}},{"start":{"line":78,"column":87},"end":{"line":78,"column":89}}],"line":78},"4":{"loc":{"start":{"line":81,"column":11},"end":{"line":81,"column":null}},"type":"binary-expr","locations":[{"start":{"line":81,"column":11},"end":{"line":81,"column":25}},{"start":{"line":81,"column":25},"end":{"line":81,"column":null}}],"line":81},"5":{"loc":{"start":{"line":88,"column":33},"end":{"line":88,"column":77}},"type":"cond-expr","locations":[{"start":{"line":88,"column":48},"end":{"line":88,"column":63}},{"start":{"line":88,"column":63},"end":{"line":88,"column":77}}],"line":88},"6":{"loc":{"start":{"line":89,"column":17},"end":{"line":89,"column":null}},"type":"cond-expr","locations":[{"start":{"line":89,"column":32},"end":{"line":89,"column":43}},{"start":{"line":89,"column":43},"end":{"line":89,"column":null}}],"line":89},"7":{"loc":{"start":{"line":97,"column":11},"end":{"line":97,"column":null}},"type":"binary-expr","locations":[{"start":{"line":97,"column":11},"end":{"line":97,"column":30}},{"start":{"line":97,"column":30},"end":{"line":97,"column":null}}],"line":97},"8":{"loc":{"start":{"line":100,"column":11},"end":{"line":103,"column":null}},"type":"binary-expr","locations":[{"start":{"line":100,"column":11},"end":{"line":100,"column":null}},{"start":{"line":101,"column":12},"end":{"line":103,"column":null}}],"line":100},"9":{"loc":{"start":{"line":106,"column":6},"end":{"line":111,"column":null}},"type":"binary-expr","locations":[{"start":{"line":107,"column":8},"end":{"line":107,"column":29}},{"start":{"line":107,"column":29},"end":{"line":107,"column":null}},{"start":{"line":108,"column":8},"end":{"line":111,"column":null}}],"line":106},"10":{"loc":{"start":{"line":110,"column":45},"end":{"line":110,"column":83}},"type":"binary-expr","locations":[{"start":{"line":110,"column":45},"end":{"line":110,"column":66}},{"start":{"line":110,"column":66},"end":{"line":110,"column":83}}],"line":110}},"s":{"0":56,"1":56,"2":56,"3":22,"4":56,"5":1,"6":56,"7":1,"8":1,"9":1,"10":1,"11":1,"12":0,"13":1,"14":1,"15":0,"16":0,"17":0,"18":56,"19":1,"20":1,"21":1,"22":1,"23":0,"24":1,"25":1,"26":1,"27":0,"28":0,"29":56,"30":1,"31":1},"f":{"0":56,"1":22,"2":1,"3":1,"4":1,"5":1,"6":0,"7":1,"8":1,"9":1,"10":0,"11":1},"b":{"0":[0,1],"1":[0,1],"2":[38,18],"3":[4,52],"4":[56,0],"5":[5,51],"6":[5,51],"7":[56,13],"8":[56,55],"9":[56,55,12],"10":[12,11]},"meta":{"lastBranch":11,"lastFunction":12,"lastStatement":32,"seen":{"f:11:24:11:33":0,"s:12:22:12:Infinity":0,"s:13:28:13:Infinity":1,"s:15:2:17:Infinity":2,"f:15:12:15:18":1,"s:16:4:16:Infinity":3,"s:19:21:21:Infinity":4,"f:19:21:19:27":2,"s:20:4:20:Infinity":5,"s:23:21:57:Infinity":6,"f:23:21:23:22":3,"s:24:4:24:Infinity":7,"s:26:25:26:Infinity":8,"s:27:4:27:Infinity":9,"s:29:4:56:Infinity":10,"f:40:12:40:13":4,"b:41:8:43:Infinity:undefined:undefined:undefined:undefined":0,"s:41:8:43:Infinity":11,"s:42:10:42:Infinity":12,"s:44:8:44:Infinity":13,"f:46:12:46:18":5,"s:49:8:49:Infinity":14,"f:51:13:51:14":6,"s:52:8:52:Infinity":15,"s:54:8:54:Infinity":16,"s:55:8:55:Infinity":17,"s:59:26:75:Infinity":18,"f:59:26:59:27":7,"s:60:4:60:Infinity":19,"s:61:4:61:Infinity":20,"s:62:4:74:Infinity":21,"f:63:12:63:13":8,"b:64:8:64:Infinity:undefined:undefined:undefined:undefined":1,"s:64:8:64:Infinity":22,"s:64:21:64:Infinity":23,"s:65:8:65:Infinity":24,"f:67:12:67:13":9,"s:68:8:68:Infinity":25,"s:69:8:69:Infinity":26,"f:71:13:71:14":10,"s:72:8:72:Infinity":27,"s:73:8:73:Infinity":28,"s:77:2:113:Infinity":29,"b:78:44:78:53:78:53:78:61":2,"b:78:75:78:87:78:87:78:89":3,"b:81:11:81:25:81:25:81:Infinity":4,"f:84:19:84:20":11,"s:85:12:85:Infinity":30,"s:86:12:86:Infinity":31,"b:88:48:88:63:88:63:88:77":5,"b:89:32:89:43:89:43:89:Infinity":6,"b:97:11:97:30:97:30:97:Infinity":7,"b:100:11:100:Infinity:101:12:103:Infinity":8,"b:107:8:107:29:107:29:107:Infinity:108:8:111:Infinity":9,"b:110:45:110:66:110:66:110:83":10}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.tsx": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.tsx","statementMap":{"0":{"start":{"line":9,"column":26},"end":{"line":9,"column":null}},"1":{"start":{"line":10,"column":21},"end":{"line":10,"column":null}},"2":{"start":{"line":11,"column":19},"end":{"line":11,"column":null}},"3":{"start":{"line":13,"column":24},"end":{"line":13,"column":null}},"4":{"start":{"line":14,"column":28},"end":{"line":14,"column":null}},"5":{"start":{"line":15,"column":36},"end":{"line":15,"column":null}},"6":{"start":{"line":16,"column":28},"end":{"line":16,"column":null}},"7":{"start":{"line":17,"column":24},"end":{"line":17,"column":null}},"8":{"start":{"line":18,"column":40},"end":{"line":18,"column":null}},"9":{"start":{"line":20,"column":21},"end":{"line":87,"column":null}},"10":{"start":{"line":21,"column":4},"end":{"line":26,"column":null}},"11":{"start":{"line":22,"column":6},"end":{"line":22,"column":null}},"12":{"start":{"line":24,"column":6},"end":{"line":24,"column":null}},"13":{"start":{"line":25,"column":6},"end":{"line":25,"column":null}},"14":{"start":{"line":27,"column":4},"end":{"line":27,"column":null}},"15":{"start":{"line":29,"column":14},"end":{"line":29,"column":null}},"16":{"start":{"line":30,"column":19},"end":{"line":30,"column":null}},"17":{"start":{"line":32,"column":4},"end":{"line":36,"column":null}},"18":{"start":{"line":33,"column":6},"end":{"line":33,"column":null}},"19":{"start":{"line":34,"column":4},"end":{"line":36,"column":null}},"20":{"start":{"line":35,"column":6},"end":{"line":35,"column":null}},"21":{"start":{"line":38,"column":4},"end":{"line":40,"column":null}},"22":{"start":{"line":39,"column":6},"end":{"line":39,"column":null}},"23":{"start":{"line":43,"column":24},"end":{"line":43,"column":null}},"24":{"start":{"line":44,"column":4},"end":{"line":46,"column":null}},"25":{"start":{"line":45,"column":6},"end":{"line":45,"column":null}},"26":{"start":{"line":48,"column":4},"end":{"line":58,"column":null}},"27":{"start":{"line":49,"column":6},"end":{"line":49,"column":null}},"28":{"start":{"line":50,"column":4},"end":{"line":58,"column":null}},"29":{"start":{"line":51,"column":6},"end":{"line":51,"column":null}},"30":{"start":{"line":52,"column":6},"end":{"line":52,"column":null}},"31":{"start":{"line":55,"column":6},"end":{"line":57,"column":null}},"32":{"start":{"line":56,"column":8},"end":{"line":56,"column":null}},"33":{"start":{"line":60,"column":24},"end":{"line":60,"column":null}},"34":{"start":{"line":61,"column":4},"end":{"line":63,"column":null}},"35":{"start":{"line":62,"column":6},"end":{"line":62,"column":null}},"36":{"start":{"line":65,"column":4},"end":{"line":86,"column":null}},"37":{"start":{"line":67,"column":8},"end":{"line":69,"column":null}},"38":{"start":{"line":68,"column":10},"end":{"line":68,"column":null}},"39":{"start":{"line":70,"column":8},"end":{"line":70,"column":null}},"40":{"start":{"line":73,"column":8},"end":{"line":77,"column":null}},"41":{"start":{"line":74,"column":10},"end":{"line":74,"column":null}},"42":{"start":{"line":74,"column":29},"end":{"line":74,"column":47}},"43":{"start":{"line":76,"column":10},"end":{"line":76,"column":null}},"44":{"start":{"line":78,"column":8},"end":{"line":78,"column":null}},"45":{"start":{"line":79,"column":8},"end":{"line":79,"column":null}},"46":{"start":{"line":80,"column":8},"end":{"line":80,"column":null}},"47":{"start":{"line":83,"column":8},"end":{"line":83,"column":null}},"48":{"start":{"line":84,"column":8},"end":{"line":84,"column":null}},"49":{"start":{"line":85,"column":8},"end":{"line":85,"column":null}},"50":{"start":{"line":89,"column":2},"end":{"line":93,"column":null}},"51":{"start":{"line":90,"column":4},"end":{"line":90,"column":null}},"52":{"start":{"line":91,"column":4},"end":{"line":91,"column":null}},"53":{"start":{"line":96,"column":23},"end":{"line":101,"column":null}},"54":{"start":{"line":97,"column":20},"end":{"line":97,"column":null}},"55":{"start":{"line":98,"column":4},"end":{"line":100,"column":null}},"56":{"start":{"line":99,"column":6},"end":{"line":99,"column":null}},"57":{"start":{"line":103,"column":21},"end":{"line":113,"column":null}},"58":{"start":{"line":104,"column":24},"end":{"line":104,"column":null}},"59":{"start":{"line":106,"column":4},"end":{"line":106,"column":null}},"60":{"start":{"line":106,"column":28},"end":{"line":106,"column":88}},"61":{"start":{"line":106,"column":50},"end":{"line":106,"column":87}},"62":{"start":{"line":108,"column":4},"end":{"line":112,"column":null}},"63":{"start":{"line":112,"column":22},"end":{"line":112,"column":63}},"64":{"start":{"line":115,"column":21},"end":{"line":125,"column":null}},"65":{"start":{"line":116,"column":24},"end":{"line":116,"column":null}},"66":{"start":{"line":118,"column":4},"end":{"line":118,"column":null}},"67":{"start":{"line":118,"column":28},"end":{"line":118,"column":88}},"68":{"start":{"line":118,"column":50},"end":{"line":118,"column":87}},"69":{"start":{"line":120,"column":4},"end":{"line":124,"column":null}},"70":{"start":{"line":124,"column":22},"end":{"line":124,"column":65}},"71":{"start":{"line":127,"column":2},"end":{"line":171,"column":null}},"72":{"start":{"line":128,"column":26},"end":{"line":166,"column":null}},"73":{"start":{"line":129,"column":6},"end":{"line":129,"column":null}},"74":{"start":{"line":129,"column":30},"end":{"line":129,"column":null}},"75":{"start":{"line":131,"column":6},"end":{"line":165,"column":null}},"76":{"start":{"line":132,"column":8},"end":{"line":149,"column":null}},"77":{"start":{"line":133,"column":28},"end":{"line":133,"column":null}},"78":{"start":{"line":134,"column":10},"end":{"line":140,"column":null}},"79":{"start":{"line":135,"column":25},"end":{"line":135,"column":null}},"80":{"start":{"line":136,"column":12},"end":{"line":138,"column":null}},"81":{"start":{"line":137,"column":14},"end":{"line":137,"column":null}},"82":{"start":{"line":139,"column":12},"end":{"line":139,"column":null}},"83":{"start":{"line":144,"column":10},"end":{"line":146,"column":null}},"84":{"start":{"line":145,"column":12},"end":{"line":145,"column":null}},"85":{"start":{"line":148,"column":10},"end":{"line":148,"column":null}},"86":{"start":{"line":150,"column":6},"end":{"line":165,"column":null}},"87":{"start":{"line":151,"column":8},"end":{"line":157,"column":null}},"88":{"start":{"line":152,"column":28},"end":{"line":152,"column":null}},"89":{"start":{"line":153,"column":10},"end":{"line":155,"column":null}},"90":{"start":{"line":154,"column":12},"end":{"line":154,"column":null}},"91":{"start":{"line":156,"column":10},"end":{"line":156,"column":null}},"92":{"start":{"line":158,"column":6},"end":{"line":165,"column":null}},"93":{"start":{"line":159,"column":8},"end":{"line":164,"column":null}},"94":{"start":{"line":160,"column":10},"end":{"line":162,"column":null}},"95":{"start":{"line":161,"column":12},"end":{"line":161,"column":null}},"96":{"start":{"line":163,"column":10},"end":{"line":163,"column":null}},"97":{"start":{"line":168,"column":4},"end":{"line":168,"column":null}},"98":{"start":{"line":169,"column":4},"end":{"line":169,"column":null}},"99":{"start":{"line":169,"column":17},"end":{"line":169,"column":null}},"100":{"start":{"line":175,"column":2},"end":{"line":220,"column":null}},"101":{"start":{"line":177,"column":25},"end":{"line":193,"column":null}},"102":{"start":{"line":179,"column":8},"end":{"line":190,"column":null}},"103":{"start":{"line":181,"column":10},"end":{"line":189,"column":null}},"104":{"start":{"line":182,"column":26},"end":{"line":182,"column":null}},"105":{"start":{"line":183,"column":12},"end":{"line":188,"column":null}},"106":{"start":{"line":184,"column":27},"end":{"line":184,"column":null}},"107":{"start":{"line":185,"column":14},"end":{"line":187,"column":null}},"108":{"start":{"line":186,"column":16},"end":{"line":186,"column":null}},"109":{"start":{"line":196,"column":29},"end":{"line":205,"column":null}},"110":{"start":{"line":198,"column":8},"end":{"line":202,"column":null}},"111":{"start":{"line":199,"column":10},"end":{"line":201,"column":null}},"112":{"start":{"line":200,"column":12},"end":{"line":200,"column":null}},"113":{"start":{"line":207,"column":4},"end":{"line":210,"column":null}},"114":{"start":{"line":208,"column":17},"end":{"line":208,"column":null}},"115":{"start":{"line":209,"column":6},"end":{"line":209,"column":null}},"116":{"start":{"line":209,"column":14},"end":{"line":209,"column":null}},"117":{"start":{"line":212,"column":21},"end":{"line":212,"column":null}},"118":{"start":{"line":213,"column":4},"end":{"line":213,"column":null}},"119":{"start":{"line":213,"column":18},"end":{"line":213,"column":null}},"120":{"start":{"line":215,"column":4},"end":{"line":218,"column":null}},"121":{"start":{"line":216,"column":6},"end":{"line":216,"column":null}},"122":{"start":{"line":217,"column":6},"end":{"line":217,"column":null}},"123":{"start":{"line":222,"column":2},"end":{"line":222,"column":null}},"124":{"start":{"line":222,"column":15},"end":{"line":222,"column":null}},"125":{"start":{"line":223,"column":2},"end":{"line":223,"column":null}},"126":{"start":{"line":223,"column":13},"end":{"line":223,"column":null}},"127":{"start":{"line":225,"column":2},"end":{"line":249,"column":null}},"128":{"start":{"line":232,"column":12},"end":{"line":240,"column":null}},"129":{"start":{"line":237,"column":29},"end":{"line":237,"column":null}}},"fnMap":{"0":{"name":"FeedItems","decl":{"start":{"line":8,"column":24},"end":{"line":8,"column":36}},"loc":{"start":{"line":8,"column":36},"end":{"line":251,"column":null}},"line":8},"1":{"name":"(anonymous_1)","decl":{"start":{"line":20,"column":21},"end":{"line":20,"column":22}},"loc":{"start":{"line":20,"column":41},"end":{"line":87,"column":null}},"line":20},"2":{"name":"(anonymous_2)","decl":{"start":{"line":66,"column":12},"end":{"line":66,"column":13}},"loc":{"start":{"line":66,"column":21},"end":{"line":71,"column":7}},"line":66},"3":{"name":"(anonymous_3)","decl":{"start":{"line":72,"column":12},"end":{"line":72,"column":13}},"loc":{"start":{"line":72,"column":22},"end":{"line":81,"column":7}},"line":72},"4":{"name":"(anonymous_4)","decl":{"start":{"line":74,"column":19},"end":{"line":74,"column":20}},"loc":{"start":{"line":74,"column":29},"end":{"line":74,"column":47}},"line":74},"5":{"name":"(anonymous_5)","decl":{"start":{"line":82,"column":13},"end":{"line":82,"column":14}},"loc":{"start":{"line":82,"column":22},"end":{"line":86,"column":7}},"line":82},"6":{"name":"(anonymous_6)","decl":{"start":{"line":89,"column":12},"end":{"line":89,"column":18}},"loc":{"start":{"line":89,"column":18},"end":{"line":93,"column":5}},"line":89},"7":{"name":"(anonymous_7)","decl":{"start":{"line":96,"column":23},"end":{"line":96,"column":24}},"loc":{"start":{"line":96,"column":42},"end":{"line":101,"column":null}},"line":96},"8":{"name":"(anonymous_8)","decl":{"start":{"line":103,"column":21},"end":{"line":103,"column":22}},"loc":{"start":{"line":103,"column":37},"end":{"line":113,"column":null}},"line":103},"9":{"name":"(anonymous_9)","decl":{"start":{"line":106,"column":13},"end":{"line":106,"column":14}},"loc":{"start":{"line":106,"column":28},"end":{"line":106,"column":88}},"line":106},"10":{"name":"(anonymous_10)","decl":{"start":{"line":106,"column":42},"end":{"line":106,"column":43}},"loc":{"start":{"line":106,"column":50},"end":{"line":106,"column":87}},"line":106},"11":{"name":"(anonymous_11)","decl":{"start":{"line":112,"column":13},"end":{"line":112,"column":14}},"loc":{"start":{"line":112,"column":22},"end":{"line":112,"column":63}},"line":112},"12":{"name":"(anonymous_12)","decl":{"start":{"line":115,"column":21},"end":{"line":115,"column":22}},"loc":{"start":{"line":115,"column":37},"end":{"line":125,"column":null}},"line":115},"13":{"name":"(anonymous_13)","decl":{"start":{"line":118,"column":13},"end":{"line":118,"column":14}},"loc":{"start":{"line":118,"column":28},"end":{"line":118,"column":88}},"line":118},"14":{"name":"(anonymous_14)","decl":{"start":{"line":118,"column":42},"end":{"line":118,"column":43}},"loc":{"start":{"line":118,"column":50},"end":{"line":118,"column":87}},"line":118},"15":{"name":"(anonymous_15)","decl":{"start":{"line":124,"column":13},"end":{"line":124,"column":14}},"loc":{"start":{"line":124,"column":22},"end":{"line":124,"column":65}},"line":124},"16":{"name":"(anonymous_16)","decl":{"start":{"line":127,"column":12},"end":{"line":127,"column":18}},"loc":{"start":{"line":127,"column":18},"end":{"line":171,"column":5}},"line":127},"17":{"name":"(anonymous_17)","decl":{"start":{"line":128,"column":26},"end":{"line":128,"column":27}},"loc":{"start":{"line":128,"column":48},"end":{"line":166,"column":null}},"line":128},"18":{"name":"(anonymous_18)","decl":{"start":{"line":132,"column":25},"end":{"line":132,"column":26}},"loc":{"start":{"line":132,"column":35},"end":{"line":149,"column":9}},"line":132},"19":{"name":"(anonymous_19)","decl":{"start":{"line":151,"column":25},"end":{"line":151,"column":26}},"loc":{"start":{"line":151,"column":35},"end":{"line":157,"column":9}},"line":151},"20":{"name":"(anonymous_20)","decl":{"start":{"line":159,"column":25},"end":{"line":159,"column":26}},"loc":{"start":{"line":159,"column":43},"end":{"line":164,"column":9}},"line":159},"21":{"name":"(anonymous_21)","decl":{"start":{"line":169,"column":11},"end":{"line":169,"column":17}},"loc":{"start":{"line":169,"column":17},"end":{"line":169,"column":null}},"line":169},"22":{"name":"(anonymous_22)","decl":{"start":{"line":175,"column":12},"end":{"line":175,"column":18}},"loc":{"start":{"line":175,"column":18},"end":{"line":220,"column":5}},"line":175},"23":{"name":"(anonymous_23)","decl":{"start":{"line":178,"column":6},"end":{"line":178,"column":7}},"loc":{"start":{"line":178,"column":19},"end":{"line":191,"column":null}},"line":178},"24":{"name":"(anonymous_24)","decl":{"start":{"line":179,"column":24},"end":{"line":179,"column":25}},"loc":{"start":{"line":179,"column":35},"end":{"line":190,"column":9}},"line":179},"25":{"name":"(anonymous_25)","decl":{"start":{"line":197,"column":6},"end":{"line":197,"column":7}},"loc":{"start":{"line":197,"column":19},"end":{"line":203,"column":null}},"line":197},"26":{"name":"(anonymous_26)","decl":{"start":{"line":198,"column":24},"end":{"line":198,"column":25}},"loc":{"start":{"line":198,"column":35},"end":{"line":202,"column":9}},"line":198},"27":{"name":"(anonymous_27)","decl":{"start":{"line":207,"column":18},"end":{"line":207,"column":19}},"loc":{"start":{"line":207,"column":32},"end":{"line":210,"column":5}},"line":207},"28":{"name":"(anonymous_28)","decl":{"start":{"line":215,"column":11},"end":{"line":215,"column":17}},"loc":{"start":{"line":215,"column":17},"end":{"line":218,"column":null}},"line":215},"29":{"name":"(anonymous_29)","decl":{"start":{"line":231,"column":21},"end":{"line":231,"column":22}},"loc":{"start":{"line":232,"column":12},"end":{"line":240,"column":null}},"line":232},"30":{"name":"(anonymous_30)","decl":{"start":{"line":237,"column":23},"end":{"line":237,"column":29}},"loc":{"start":{"line":237,"column":29},"end":{"line":237,"column":null}},"line":237}},"branchMap":{"0":{"loc":{"start":{"line":11,"column":19},"end":{"line":11,"column":null}},"type":"binary-expr","locations":[{"start":{"line":11,"column":19},"end":{"line":11,"column":49}},{"start":{"line":11,"column":49},"end":{"line":11,"column":null}}],"line":11},"1":{"loc":{"start":{"line":21,"column":4},"end":{"line":26,"column":null}},"type":"if","locations":[{"start":{"line":21,"column":4},"end":{"line":26,"column":null}},{"start":{"line":23,"column":11},"end":{"line":26,"column":null}}],"line":21},"2":{"loc":{"start":{"line":32,"column":4},"end":{"line":36,"column":null}},"type":"if","locations":[{"start":{"line":32,"column":4},"end":{"line":36,"column":null}},{"start":{"line":34,"column":4},"end":{"line":36,"column":null}}],"line":32},"3":{"loc":{"start":{"line":34,"column":4},"end":{"line":36,"column":null}},"type":"if","locations":[{"start":{"line":34,"column":4},"end":{"line":36,"column":null}},{"start":{},"end":{}}],"line":34},"4":{"loc":{"start":{"line":38,"column":4},"end":{"line":40,"column":null}},"type":"if","locations":[{"start":{"line":38,"column":4},"end":{"line":40,"column":null}},{"start":{},"end":{}}],"line":38},"5":{"loc":{"start":{"line":44,"column":4},"end":{"line":46,"column":null}},"type":"if","locations":[{"start":{"line":44,"column":4},"end":{"line":46,"column":null}},{"start":{},"end":{}}],"line":44},"6":{"loc":{"start":{"line":48,"column":4},"end":{"line":58,"column":null}},"type":"if","locations":[{"start":{"line":48,"column":4},"end":{"line":58,"column":null}},{"start":{"line":50,"column":4},"end":{"line":58,"column":null}}],"line":48},"7":{"loc":{"start":{"line":50,"column":4},"end":{"line":58,"column":null}},"type":"if","locations":[{"start":{"line":50,"column":4},"end":{"line":58,"column":null}},{"start":{"line":53,"column":11},"end":{"line":58,"column":null}}],"line":50},"8":{"loc":{"start":{"line":55,"column":6},"end":{"line":57,"column":null}},"type":"if","locations":[{"start":{"line":55,"column":6},"end":{"line":57,"column":null}},{"start":{},"end":{}}],"line":55},"9":{"loc":{"start":{"line":61,"column":4},"end":{"line":63,"column":null}},"type":"if","locations":[{"start":{"line":61,"column":4},"end":{"line":63,"column":null}},{"start":{},"end":{}}],"line":61},"10":{"loc":{"start":{"line":67,"column":8},"end":{"line":69,"column":null}},"type":"if","locations":[{"start":{"line":67,"column":8},"end":{"line":69,"column":null}},{"start":{},"end":{}}],"line":67},"11":{"loc":{"start":{"line":73,"column":8},"end":{"line":77,"column":null}},"type":"if","locations":[{"start":{"line":73,"column":8},"end":{"line":77,"column":null}},{"start":{"line":75,"column":15},"end":{"line":77,"column":null}}],"line":73},"12":{"loc":{"start":{"line":98,"column":4},"end":{"line":100,"column":null}},"type":"if","locations":[{"start":{"line":98,"column":4},"end":{"line":100,"column":null}},{"start":{},"end":{}}],"line":98},"13":{"loc":{"start":{"line":106,"column":50},"end":{"line":106,"column":87}},"type":"cond-expr","locations":[{"start":{"line":106,"column":71},"end":{"line":106,"column":85}},{"start":{"line":106,"column":85},"end":{"line":106,"column":87}}],"line":106},"14":{"loc":{"start":{"line":118,"column":50},"end":{"line":118,"column":87}},"type":"cond-expr","locations":[{"start":{"line":118,"column":71},"end":{"line":118,"column":85}},{"start":{"line":118,"column":85},"end":{"line":118,"column":87}}],"line":118},"15":{"loc":{"start":{"line":129,"column":6},"end":{"line":129,"column":null}},"type":"if","locations":[{"start":{"line":129,"column":6},"end":{"line":129,"column":null}},{"start":{},"end":{}}],"line":129},"16":{"loc":{"start":{"line":131,"column":6},"end":{"line":165,"column":null}},"type":"if","locations":[{"start":{"line":131,"column":6},"end":{"line":165,"column":null}},{"start":{"line":150,"column":6},"end":{"line":165,"column":null}}],"line":131},"17":{"loc":{"start":{"line":134,"column":10},"end":{"line":140,"column":null}},"type":"if","locations":[{"start":{"line":134,"column":10},"end":{"line":140,"column":null}},{"start":{},"end":{}}],"line":134},"18":{"loc":{"start":{"line":136,"column":12},"end":{"line":138,"column":null}},"type":"if","locations":[{"start":{"line":136,"column":12},"end":{"line":138,"column":null}},{"start":{},"end":{}}],"line":136},"19":{"loc":{"start":{"line":144,"column":10},"end":{"line":146,"column":null}},"type":"if","locations":[{"start":{"line":144,"column":10},"end":{"line":146,"column":null}},{"start":{},"end":{}}],"line":144},"20":{"loc":{"start":{"line":144,"column":14},"end":{"line":144,"column":73}},"type":"binary-expr","locations":[{"start":{"line":144,"column":14},"end":{"line":144,"column":48}},{"start":{"line":144,"column":48},"end":{"line":144,"column":59}},{"start":{"line":144,"column":59},"end":{"line":144,"column":73}}],"line":144},"21":{"loc":{"start":{"line":150,"column":6},"end":{"line":165,"column":null}},"type":"if","locations":[{"start":{"line":150,"column":6},"end":{"line":165,"column":null}},{"start":{"line":158,"column":6},"end":{"line":165,"column":null}}],"line":150},"22":{"loc":{"start":{"line":153,"column":10},"end":{"line":155,"column":null}},"type":"if","locations":[{"start":{"line":153,"column":10},"end":{"line":155,"column":null}},{"start":{},"end":{}}],"line":153},"23":{"loc":{"start":{"line":158,"column":6},"end":{"line":165,"column":null}},"type":"if","locations":[{"start":{"line":158,"column":6},"end":{"line":165,"column":null}},{"start":{},"end":{}}],"line":158},"24":{"loc":{"start":{"line":160,"column":10},"end":{"line":162,"column":null}},"type":"if","locations":[{"start":{"line":160,"column":10},"end":{"line":162,"column":null}},{"start":{},"end":{}}],"line":160},"25":{"loc":{"start":{"line":160,"column":14},"end":{"line":160,"column":64}},"type":"binary-expr","locations":[{"start":{"line":160,"column":14},"end":{"line":160,"column":35}},{"start":{"line":160,"column":35},"end":{"line":160,"column":64}}],"line":160},"26":{"loc":{"start":{"line":181,"column":10},"end":{"line":189,"column":null}},"type":"if","locations":[{"start":{"line":181,"column":10},"end":{"line":189,"column":null}},{"start":{},"end":{}}],"line":181},"27":{"loc":{"start":{"line":181,"column":14},"end":{"line":181,"column":73}},"type":"binary-expr","locations":[{"start":{"line":181,"column":14},"end":{"line":181,"column":39}},{"start":{"line":181,"column":39},"end":{"line":181,"column":73}}],"line":181},"28":{"loc":{"start":{"line":183,"column":12},"end":{"line":188,"column":null}},"type":"if","locations":[{"start":{"line":183,"column":12},"end":{"line":188,"column":null}},{"start":{},"end":{}}],"line":183},"29":{"loc":{"start":{"line":183,"column":16},"end":{"line":183,"column":69}},"type":"binary-expr","locations":[{"start":{"line":183,"column":16},"end":{"line":183,"column":33}},{"start":{"line":183,"column":33},"end":{"line":183,"column":47}},{"start":{"line":183,"column":47},"end":{"line":183,"column":69}}],"line":183},"30":{"loc":{"start":{"line":185,"column":14},"end":{"line":187,"column":null}},"type":"if","locations":[{"start":{"line":185,"column":14},"end":{"line":187,"column":null}},{"start":{},"end":{}}],"line":185},"31":{"loc":{"start":{"line":199,"column":10},"end":{"line":201,"column":null}},"type":"if","locations":[{"start":{"line":199,"column":10},"end":{"line":201,"column":null}},{"start":{},"end":{}}],"line":199},"32":{"loc":{"start":{"line":199,"column":14},"end":{"line":199,"column":83}},"type":"binary-expr","locations":[{"start":{"line":199,"column":14},"end":{"line":199,"column":38}},{"start":{"line":199,"column":38},"end":{"line":199,"column":54}},{"start":{"line":199,"column":54},"end":{"line":199,"column":65}},{"start":{"line":199,"column":65},"end":{"line":199,"column":83}}],"line":199},"33":{"loc":{"start":{"line":209,"column":6},"end":{"line":209,"column":null}},"type":"if","locations":[{"start":{"line":209,"column":6},"end":{"line":209,"column":null}},{"start":{},"end":{}}],"line":209},"34":{"loc":{"start":{"line":213,"column":4},"end":{"line":213,"column":null}},"type":"if","locations":[{"start":{"line":213,"column":4},"end":{"line":213,"column":null}},{"start":{},"end":{}}],"line":213},"35":{"loc":{"start":{"line":222,"column":2},"end":{"line":222,"column":null}},"type":"if","locations":[{"start":{"line":222,"column":2},"end":{"line":222,"column":null}},{"start":{},"end":{}}],"line":222},"36":{"loc":{"start":{"line":223,"column":2},"end":{"line":223,"column":null}},"type":"if","locations":[{"start":{"line":223,"column":2},"end":{"line":223,"column":null}},{"start":{},"end":{}}],"line":223},"37":{"loc":{"start":{"line":227,"column":7},"end":{"line":247,"column":null}},"type":"cond-expr","locations":[{"start":{"line":228,"column":8},"end":{"line":228,"column":null}},{"start":{"line":230,"column":8},"end":{"line":247,"column":null}}],"line":227},"38":{"loc":{"start":{"line":242,"column":11},"end":{"line":245,"column":null}},"type":"binary-expr","locations":[{"start":{"line":242,"column":11},"end":{"line":242,"column":null}},{"start":{"line":243,"column":12},"end":{"line":245,"column":null}}],"line":242},"39":{"loc":{"start":{"line":244,"column":15},"end":{"line":244,"column":null}},"type":"cond-expr","locations":[{"start":{"line":244,"column":29},"end":{"line":244,"column":49}},{"start":{"line":244,"column":49},"end":{"line":244,"column":null}}],"line":244}},"s":{"0":36,"1":36,"2":36,"3":36,"4":36,"5":36,"6":36,"7":36,"8":36,"9":36,"10":11,"11":3,"12":8,"13":8,"14":11,"15":11,"16":11,"17":11,"18":2,"19":9,"20":1,"21":11,"22":3,"23":11,"24":11,"25":0,"26":11,"27":0,"28":11,"29":0,"30":0,"31":11,"32":11,"33":11,"34":11,"35":11,"36":11,"37":10,"38":0,"39":10,"40":9,"41":3,"42":3,"43":6,"44":9,"45":9,"46":9,"47":1,"48":1,"49":1,"50":36,"51":8,"52":8,"53":36,"54":5,"55":5,"56":5,"57":36,"58":2,"59":2,"60":2,"61":3,"62":2,"63":0,"64":36,"65":1,"66":1,"67":1,"68":2,"69":1,"70":0,"71":36,"72":31,"73":6,"74":0,"75":6,"76":5,"77":5,"78":5,"79":5,"80":5,"81":1,"82":5,"83":5,"84":2,"85":5,"86":1,"87":0,"88":0,"89":0,"90":0,"91":0,"92":1,"93":1,"94":1,"95":1,"96":1,"97":31,"98":31,"99":31,"100":36,"101":31,"102":1,"103":1,"104":1,"105":1,"106":1,"107":1,"108":1,"109":31,"110":1,"111":1,"112":1,"113":31,"114":31,"115":31,"116":31,"117":31,"118":31,"119":15,"120":31,"121":31,"122":31,"123":36,"124":15,"125":21,"126":21,"127":20,"128":44,"129":0},"f":{"0":36,"1":11,"2":10,"3":9,"4":3,"5":1,"6":8,"7":5,"8":2,"9":2,"10":3,"11":0,"12":1,"13":1,"14":2,"15":0,"16":31,"17":6,"18":5,"19":0,"20":1,"21":31,"22":31,"23":1,"24":1,"25":1,"26":1,"27":31,"28":31,"29":44,"30":0},"b":{"0":[36,36],"1":[3,8],"2":[2,9],"3":[1,8],"4":[3,8],"5":[0,11],"6":[0,11],"7":[0,11],"8":[11,0],"9":[11,0],"10":[0,10],"11":[3,6],"12":[5,0],"13":[2,1],"14":[1,1],"15":[0,6],"16":[5,1],"17":[5,0],"18":[1,4],"19":[2,3],"20":[5,2,2],"21":[0,1],"22":[0,0],"23":[1,0],"24":[1,0],"25":[1,1],"26":[1,0],"27":[1,1],"28":[1,0],"29":[1,1,1],"30":[1,0],"31":[1,0],"32":[1,1,1,1],"33":[31,0],"34":[15,16],"35":[15,21],"36":[1,20],"37":[0,20],"38":[20,20],"39":[5,15]},"meta":{"lastBranch":40,"lastFunction":31,"lastStatement":130,"seen":{"f:8:24:8:36":0,"s:9:26:9:Infinity":0,"s:10:21:10:Infinity":1,"s:11:19:11:Infinity":2,"b:11:19:11:49:11:49:11:Infinity":0,"s:13:24:13:Infinity":3,"s:14:28:14:Infinity":4,"s:15:36:15:Infinity":5,"s:16:28:16:Infinity":6,"s:17:24:17:Infinity":7,"s:18:40:18:Infinity":8,"s:20:21:87:Infinity":9,"f:20:21:20:22":1,"b:21:4:26:Infinity:23:11:26:Infinity":1,"s:21:4:26:Infinity":10,"s:22:6:22:Infinity":11,"s:24:6:24:Infinity":12,"s:25:6:25:Infinity":13,"s:27:4:27:Infinity":14,"s:29:14:29:Infinity":15,"s:30:19:30:Infinity":16,"b:32:4:36:Infinity:34:4:36:Infinity":2,"s:32:4:36:Infinity":17,"s:33:6:33:Infinity":18,"b:34:4:36:Infinity:undefined:undefined:undefined:undefined":3,"s:34:4:36:Infinity":19,"s:35:6:35:Infinity":20,"b:38:4:40:Infinity:undefined:undefined:undefined:undefined":4,"s:38:4:40:Infinity":21,"s:39:6:39:Infinity":22,"s:43:24:43:Infinity":23,"b:44:4:46:Infinity:undefined:undefined:undefined:undefined":5,"s:44:4:46:Infinity":24,"s:45:6:45:Infinity":25,"b:48:4:58:Infinity:50:4:58:Infinity":6,"s:48:4:58:Infinity":26,"s:49:6:49:Infinity":27,"b:50:4:58:Infinity:53:11:58:Infinity":7,"s:50:4:58:Infinity":28,"s:51:6:51:Infinity":29,"s:52:6:52:Infinity":30,"b:55:6:57:Infinity:undefined:undefined:undefined:undefined":8,"s:55:6:57:Infinity":31,"s:56:8:56:Infinity":32,"s:60:24:60:Infinity":33,"b:61:4:63:Infinity:undefined:undefined:undefined:undefined":9,"s:61:4:63:Infinity":34,"s:62:6:62:Infinity":35,"s:65:4:86:Infinity":36,"f:66:12:66:13":2,"b:67:8:69:Infinity:undefined:undefined:undefined:undefined":10,"s:67:8:69:Infinity":37,"s:68:10:68:Infinity":38,"s:70:8:70:Infinity":39,"f:72:12:72:13":3,"b:73:8:77:Infinity:75:15:77:Infinity":11,"s:73:8:77:Infinity":40,"s:74:10:74:Infinity":41,"f:74:19:74:20":4,"s:74:29:74:47":42,"s:76:10:76:Infinity":43,"s:78:8:78:Infinity":44,"s:79:8:79:Infinity":45,"s:80:8:80:Infinity":46,"f:82:13:82:14":5,"s:83:8:83:Infinity":47,"s:84:8:84:Infinity":48,"s:85:8:85:Infinity":49,"s:89:2:93:Infinity":50,"f:89:12:89:18":6,"s:90:4:90:Infinity":51,"s:91:4:91:Infinity":52,"s:96:23:101:Infinity":53,"f:96:23:96:24":7,"s:97:20:97:Infinity":54,"b:98:4:100:Infinity:undefined:undefined:undefined:undefined":12,"s:98:4:100:Infinity":55,"s:99:6:99:Infinity":56,"s:103:21:113:Infinity":57,"f:103:21:103:22":8,"s:104:24:104:Infinity":58,"s:106:4:106:Infinity":59,"f:106:13:106:14":9,"s:106:28:106:88":60,"f:106:42:106:43":10,"s:106:50:106:87":61,"b:106:71:106:85:106:85:106:87":13,"s:108:4:112:Infinity":62,"f:112:13:112:14":11,"s:112:22:112:63":63,"s:115:21:125:Infinity":64,"f:115:21:115:22":12,"s:116:24:116:Infinity":65,"s:118:4:118:Infinity":66,"f:118:13:118:14":13,"s:118:28:118:88":67,"f:118:42:118:43":14,"s:118:50:118:87":68,"b:118:71:118:85:118:85:118:87":14,"s:120:4:124:Infinity":69,"f:124:13:124:14":15,"s:124:22:124:65":70,"s:127:2:171:Infinity":71,"f:127:12:127:18":16,"s:128:26:166:Infinity":72,"f:128:26:128:27":17,"b:129:6:129:Infinity:undefined:undefined:undefined:undefined":15,"s:129:6:129:Infinity":73,"s:129:30:129:Infinity":74,"b:131:6:165:Infinity:150:6:165:Infinity":16,"s:131:6:165:Infinity":75,"s:132:8:149:Infinity":76,"f:132:25:132:26":18,"s:133:28:133:Infinity":77,"b:134:10:140:Infinity:undefined:undefined:undefined:undefined":17,"s:134:10:140:Infinity":78,"s:135:25:135:Infinity":79,"b:136:12:138:Infinity:undefined:undefined:undefined:undefined":18,"s:136:12:138:Infinity":80,"s:137:14:137:Infinity":81,"s:139:12:139:Infinity":82,"b:144:10:146:Infinity:undefined:undefined:undefined:undefined":19,"s:144:10:146:Infinity":83,"b:144:14:144:48:144:48:144:59:144:59:144:73":20,"s:145:12:145:Infinity":84,"s:148:10:148:Infinity":85,"b:150:6:165:Infinity:158:6:165:Infinity":21,"s:150:6:165:Infinity":86,"s:151:8:157:Infinity":87,"f:151:25:151:26":19,"s:152:28:152:Infinity":88,"b:153:10:155:Infinity:undefined:undefined:undefined:undefined":22,"s:153:10:155:Infinity":89,"s:154:12:154:Infinity":90,"s:156:10:156:Infinity":91,"b:158:6:165:Infinity:undefined:undefined:undefined:undefined":23,"s:158:6:165:Infinity":92,"s:159:8:164:Infinity":93,"f:159:25:159:26":20,"b:160:10:162:Infinity:undefined:undefined:undefined:undefined":24,"s:160:10:162:Infinity":94,"b:160:14:160:35:160:35:160:64":25,"s:161:12:161:Infinity":95,"s:163:10:163:Infinity":96,"s:168:4:168:Infinity":97,"s:169:4:169:Infinity":98,"f:169:11:169:17":21,"s:169:17:169:Infinity":99,"s:175:2:220:Infinity":100,"f:175:12:175:18":22,"s:177:25:193:Infinity":101,"f:178:6:178:7":23,"s:179:8:190:Infinity":102,"f:179:24:179:25":24,"b:181:10:189:Infinity:undefined:undefined:undefined:undefined":26,"s:181:10:189:Infinity":103,"b:181:14:181:39:181:39:181:73":27,"s:182:26:182:Infinity":104,"b:183:12:188:Infinity:undefined:undefined:undefined:undefined":28,"s:183:12:188:Infinity":105,"b:183:16:183:33:183:33:183:47:183:47:183:69":29,"s:184:27:184:Infinity":106,"b:185:14:187:Infinity:undefined:undefined:undefined:undefined":30,"s:185:14:187:Infinity":107,"s:186:16:186:Infinity":108,"s:196:29:205:Infinity":109,"f:197:6:197:7":25,"s:198:8:202:Infinity":110,"f:198:24:198:25":26,"b:199:10:201:Infinity:undefined:undefined:undefined:undefined":31,"s:199:10:201:Infinity":111,"b:199:14:199:38:199:38:199:54:199:54:199:65:199:65:199:83":32,"s:200:12:200:Infinity":112,"s:207:4:210:Infinity":113,"f:207:18:207:19":27,"s:208:17:208:Infinity":114,"b:209:6:209:Infinity:undefined:undefined:undefined:undefined":33,"s:209:6:209:Infinity":115,"s:209:14:209:Infinity":116,"s:212:21:212:Infinity":117,"b:213:4:213:Infinity:undefined:undefined:undefined:undefined":34,"s:213:4:213:Infinity":118,"s:213:18:213:Infinity":119,"s:215:4:218:Infinity":120,"f:215:11:215:17":28,"s:216:6:216:Infinity":121,"s:217:6:217:Infinity":122,"b:222:2:222:Infinity:undefined:undefined:undefined:undefined":35,"s:222:2:222:Infinity":123,"s:222:15:222:Infinity":124,"b:223:2:223:Infinity:undefined:undefined:undefined:undefined":36,"s:223:2:223:Infinity":125,"s:223:13:223:Infinity":126,"s:225:2:249:Infinity":127,"b:228:8:228:Infinity:230:8:247:Infinity":37,"f:231:21:231:22":29,"s:232:12:240:Infinity":128,"f:237:23:237:29":30,"s:237:29:237:Infinity":129,"b:242:11:242:Infinity:243:12:245:Infinity":38,"b:244:29:244:49:244:49:244:Infinity":39}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.tsx": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.tsx","statementMap":{"0":{"start":{"line":19,"column":24},"end":{"line":19,"column":null}},"1":{"start":{"line":20,"column":22},"end":{"line":20,"column":null}},"2":{"start":{"line":21,"column":28},"end":{"line":21,"column":null}},"3":{"start":{"line":22,"column":24},"end":{"line":22,"column":null}},"4":{"start":{"line":23,"column":40},"end":{"line":23,"column":null}},"5":{"start":{"line":24,"column":38},"end":{"line":24,"column":null}},"6":{"start":{"line":25,"column":36},"end":{"line":25,"column":null}},"7":{"start":{"line":26,"column":8},"end":{"line":26,"column":null}},"8":{"start":{"line":27,"column":21},"end":{"line":27,"column":null}},"9":{"start":{"line":28,"column":8},"end":{"line":28,"column":null}},"10":{"start":{"line":29,"column":26},"end":{"line":29,"column":null}},"11":{"start":{"line":31,"column":25},"end":{"line":31,"column":null}},"12":{"start":{"line":33,"column":2},"end":{"line":38,"column":null}},"13":{"start":{"line":34,"column":20},"end":{"line":34,"column":null}},"14":{"start":{"line":35,"column":4},"end":{"line":37,"column":null}},"15":{"start":{"line":36,"column":6},"end":{"line":36,"column":null}},"16":{"start":{"line":41,"column":4},"end":{"line":42,"column":null}},"17":{"start":{"line":44,"column":23},"end":{"line":49,"column":null}},"18":{"start":{"line":45,"column":4},"end":{"line":45,"column":null}},"19":{"start":{"line":46,"column":4},"end":{"line":48,"column":null}},"20":{"start":{"line":47,"column":6},"end":{"line":47,"column":null}},"21":{"start":{"line":51,"column":22},"end":{"line":53,"column":null}},"22":{"start":{"line":52,"column":4},"end":{"line":52,"column":null}},"23":{"start":{"line":55,"column":21},"end":{"line":57,"column":null}},"24":{"start":{"line":56,"column":4},"end":{"line":56,"column":null}},"25":{"start":{"line":59,"column":26},"end":{"line":63,"column":null}},"26":{"start":{"line":60,"column":4},"end":{"line":62,"column":null}},"27":{"start":{"line":61,"column":6},"end":{"line":61,"column":null}},"28":{"start":{"line":65,"column":2},"end":{"line":85,"column":null}},"29":{"start":{"line":66,"column":4},"end":{"line":84,"column":null}},"30":{"start":{"line":68,"column":8},"end":{"line":68,"column":null}},"31":{"start":{"line":68,"column":21},"end":{"line":68,"column":null}},"32":{"start":{"line":69,"column":8},"end":{"line":69,"column":null}},"33":{"start":{"line":72,"column":8},"end":{"line":72,"column":null}},"34":{"start":{"line":72,"column":21},"end":{"line":72,"column":null}},"35":{"start":{"line":73,"column":8},"end":{"line":73,"column":null}},"36":{"start":{"line":77,"column":8},"end":{"line":77,"column":null}},"37":{"start":{"line":78,"column":8},"end":{"line":78,"column":null}},"38":{"start":{"line":79,"column":8},"end":{"line":79,"column":null}},"39":{"start":{"line":82,"column":8},"end":{"line":82,"column":null}},"40":{"start":{"line":83,"column":8},"end":{"line":83,"column":null}},"41":{"start":{"line":87,"column":2},"end":{"line":87,"column":null}},"42":{"start":{"line":87,"column":15},"end":{"line":87,"column":null}},"43":{"start":{"line":88,"column":2},"end":{"line":88,"column":null}},"44":{"start":{"line":88,"column":13},"end":{"line":88,"column":null}},"45":{"start":{"line":90,"column":23},"end":{"line":92,"column":null}},"46":{"start":{"line":91,"column":4},"end":{"line":91,"column":null}},"47":{"start":{"line":91,"column":60},"end":{"line":91,"column":95}},"48":{"start":{"line":94,"column":2},"end":{"line":210,"column":null}},"49":{"start":{"line":96,"column":42},"end":{"line":96,"column":68}},"50":{"start":{"line":106,"column":29},"end":{"line":106,"column":null}},"51":{"start":{"line":139,"column":14},"end":{"line":147,"column":null}},"52":{"start":{"line":163,"column":16},"end":{"line":171,"column":null}},"53":{"start":{"line":195,"column":27},"end":{"line":195,"column":null}},"54":{"start":{"line":202,"column":27},"end":{"line":202,"column":null}}},"fnMap":{"0":{"name":"FeedList","decl":{"start":{"line":8,"column":24},"end":{"line":8,"column":33}},"loc":{"start":{"line":18,"column":3},"end":{"line":212,"column":null}},"line":18},"1":{"name":"(anonymous_1)","decl":{"start":{"line":33,"column":12},"end":{"line":33,"column":18}},"loc":{"start":{"line":33,"column":18},"end":{"line":38,"column":5}},"line":33},"2":{"name":"(anonymous_2)","decl":{"start":{"line":44,"column":23},"end":{"line":44,"column":24}},"loc":{"start":{"line":44,"column":47},"end":{"line":49,"column":null}},"line":44},"3":{"name":"(anonymous_3)","decl":{"start":{"line":51,"column":22},"end":{"line":51,"column":28}},"loc":{"start":{"line":51,"column":28},"end":{"line":53,"column":null}},"line":51},"4":{"name":"(anonymous_4)","decl":{"start":{"line":55,"column":21},"end":{"line":55,"column":27}},"loc":{"start":{"line":55,"column":27},"end":{"line":57,"column":null}},"line":55},"5":{"name":"(anonymous_5)","decl":{"start":{"line":59,"column":26},"end":{"line":59,"column":32}},"loc":{"start":{"line":59,"column":32},"end":{"line":63,"column":null}},"line":59},"6":{"name":"(anonymous_6)","decl":{"start":{"line":65,"column":12},"end":{"line":65,"column":18}},"loc":{"start":{"line":65,"column":18},"end":{"line":85,"column":5}},"line":65},"7":{"name":"(anonymous_7)","decl":{"start":{"line":67,"column":34},"end":{"line":67,"column":35}},"loc":{"start":{"line":67,"column":43},"end":{"line":70,"column":7}},"line":67},"8":{"name":"(anonymous_8)","decl":{"start":{"line":71,"column":32},"end":{"line":71,"column":33}},"loc":{"start":{"line":71,"column":41},"end":{"line":74,"column":7}},"line":71},"9":{"name":"(anonymous_9)","decl":{"start":{"line":76,"column":12},"end":{"line":76,"column":13}},"loc":{"start":{"line":76,"column":39},"end":{"line":80,"column":7}},"line":76},"10":{"name":"(anonymous_10)","decl":{"start":{"line":81,"column":13},"end":{"line":81,"column":14}},"loc":{"start":{"line":81,"column":22},"end":{"line":84,"column":7}},"line":81},"11":{"name":"(anonymous_11)","decl":{"start":{"line":90,"column":23},"end":{"line":90,"column":29}},"loc":{"start":{"line":90,"column":29},"end":{"line":92,"column":null}},"line":90},"12":{"name":"(anonymous_12)","decl":{"start":{"line":91,"column":53},"end":{"line":91,"column":60}},"loc":{"start":{"line":91,"column":60},"end":{"line":91,"column":95}},"line":91},"13":{"name":"(anonymous_13)","decl":{"start":{"line":96,"column":36},"end":{"line":96,"column":42}},"loc":{"start":{"line":96,"column":42},"end":{"line":96,"column":68}},"line":96},"14":{"name":"(anonymous_14)","decl":{"start":{"line":106,"column":22},"end":{"line":106,"column":23}},"loc":{"start":{"line":106,"column":29},"end":{"line":106,"column":null}},"line":106},"15":{"name":"(anonymous_15)","decl":{"start":{"line":138,"column":22},"end":{"line":138,"column":23}},"loc":{"start":{"line":139,"column":14},"end":{"line":147,"column":null}},"line":139},"16":{"name":"(anonymous_16)","decl":{"start":{"line":162,"column":25},"end":{"line":162,"column":26}},"loc":{"start":{"line":163,"column":16},"end":{"line":171,"column":null}},"line":163},"17":{"name":"(anonymous_17)","decl":{"start":{"line":195,"column":21},"end":{"line":195,"column":27}},"loc":{"start":{"line":195,"column":27},"end":{"line":195,"column":null}},"line":195},"18":{"name":"(anonymous_18)","decl":{"start":{"line":202,"column":21},"end":{"line":202,"column":27}},"loc":{"start":{"line":202,"column":27},"end":{"line":202,"column":null}},"line":202}},"branchMap":{"0":{"loc":{"start":{"line":31,"column":25},"end":{"line":31,"column":null}},"type":"binary-expr","locations":[{"start":{"line":31,"column":25},"end":{"line":31,"column":56}},{"start":{"line":31,"column":56},"end":{"line":31,"column":104}},{"start":{"line":31,"column":104},"end":{"line":31,"column":null}}],"line":31},"1":{"loc":{"start":{"line":35,"column":4},"end":{"line":37,"column":null}},"type":"if","locations":[{"start":{"line":35,"column":4},"end":{"line":37,"column":null}},{"start":{},"end":{}}],"line":35},"2":{"loc":{"start":{"line":41,"column":4},"end":{"line":42,"column":null}},"type":"binary-expr","locations":[{"start":{"line":41,"column":4},"end":{"line":41,"column":null}},{"start":{"line":42,"column":5},"end":{"line":42,"column":null}}],"line":41},"3":{"loc":{"start":{"line":42,"column":5},"end":{"line":42,"column":null}},"type":"cond-expr","locations":[{"start":{"line":42,"column":56},"end":{"line":42,"column":67}},{"start":{"line":42,"column":67},"end":{"line":42,"column":null}}],"line":42},"4":{"loc":{"start":{"line":42,"column":5},"end":{"line":42,"column":56}},"type":"binary-expr","locations":[{"start":{"line":42,"column":5},"end":{"line":42,"column":34}},{"start":{"line":42,"column":34},"end":{"line":42,"column":45}},{"start":{"line":42,"column":45},"end":{"line":42,"column":56}}],"line":42},"5":{"loc":{"start":{"line":46,"column":4},"end":{"line":48,"column":null}},"type":"if","locations":[{"start":{"line":46,"column":4},"end":{"line":48,"column":null}},{"start":{},"end":{}}],"line":46},"6":{"loc":{"start":{"line":60,"column":4},"end":{"line":62,"column":null}},"type":"if","locations":[{"start":{"line":60,"column":4},"end":{"line":62,"column":null}},{"start":{},"end":{}}],"line":60},"7":{"loc":{"start":{"line":68,"column":8},"end":{"line":68,"column":null}},"type":"if","locations":[{"start":{"line":68,"column":8},"end":{"line":68,"column":null}},{"start":{},"end":{}}],"line":68},"8":{"loc":{"start":{"line":72,"column":8},"end":{"line":72,"column":null}},"type":"if","locations":[{"start":{"line":72,"column":8},"end":{"line":72,"column":null}},{"start":{},"end":{}}],"line":72},"9":{"loc":{"start":{"line":87,"column":2},"end":{"line":87,"column":null}},"type":"if","locations":[{"start":{"line":87,"column":2},"end":{"line":87,"column":null}},{"start":{},"end":{}}],"line":87},"10":{"loc":{"start":{"line":88,"column":2},"end":{"line":88,"column":null}},"type":"if","locations":[{"start":{"line":88,"column":2},"end":{"line":88,"column":null}},{"start":{},"end":{}}],"line":88},"11":{"loc":{"start":{"line":115,"column":50},"end":{"line":115,"column":94}},"type":"cond-expr","locations":[{"start":{"line":115,"column":79},"end":{"line":115,"column":90}},{"start":{"line":115,"column":90},"end":{"line":115,"column":94}}],"line":115},"12":{"loc":{"start":{"line":120,"column":47},"end":{"line":120,"column":88}},"type":"cond-expr","locations":[{"start":{"line":120,"column":73},"end":{"line":120,"column":84}},{"start":{"line":120,"column":84},"end":{"line":120,"column":88}}],"line":120},"13":{"loc":{"start":{"line":125,"column":51},"end":{"line":125,"column":96}},"type":"cond-expr","locations":[{"start":{"line":125,"column":81},"end":{"line":125,"column":92}},{"start":{"line":125,"column":92},"end":{"line":125,"column":96}}],"line":125},"14":{"loc":{"start":{"line":134,"column":36},"end":{"line":134,"column":66}},"type":"cond-expr","locations":[{"start":{"line":134,"column":51},"end":{"line":134,"column":64}},{"start":{"line":134,"column":64},"end":{"line":134,"column":66}}],"line":134},"15":{"loc":{"start":{"line":136,"column":9},"end":{"line":149,"column":null}},"type":"binary-expr","locations":[{"start":{"line":136,"column":9},"end":{"line":136,"column":null}},{"start":{"line":137,"column":10},"end":{"line":149,"column":null}}],"line":136},"16":{"loc":{"start":{"line":142,"column":41},"end":{"line":142,"column":78}},"type":"cond-expr","locations":[{"start":{"line":142,"column":65},"end":{"line":142,"column":76}},{"start":{"line":142,"column":76},"end":{"line":142,"column":78}}],"line":142},"17":{"loc":{"start":{"line":155,"column":36},"end":{"line":155,"column":67}},"type":"cond-expr","locations":[{"start":{"line":155,"column":52},"end":{"line":155,"column":65}},{"start":{"line":155,"column":65},"end":{"line":155,"column":67}}],"line":155},"18":{"loc":{"start":{"line":157,"column":9},"end":{"line":173,"column":null}},"type":"binary-expr","locations":[{"start":{"line":157,"column":9},"end":{"line":157,"column":null}},{"start":{"line":158,"column":11},"end":{"line":173,"column":null}}],"line":157},"19":{"loc":{"start":{"line":158,"column":11},"end":{"line":173,"column":null}},"type":"cond-expr","locations":[{"start":{"line":159,"column":12},"end":{"line":159,"column":null}},{"start":{"line":161,"column":12},"end":{"line":173,"column":null}}],"line":158},"20":{"loc":{"start":{"line":166,"column":45},"end":{"line":166,"column":88}},"type":"cond-expr","locations":[{"start":{"line":166,"column":75},"end":{"line":166,"column":86}},{"start":{"line":166,"column":86},"end":{"line":166,"column":88}}],"line":166},"21":{"loc":{"start":{"line":169,"column":21},"end":{"line":169,"column":null}},"type":"binary-expr","locations":[{"start":{"line":169,"column":21},"end":{"line":169,"column":35}},{"start":{"line":169,"column":35},"end":{"line":169,"column":null}}],"line":169},"22":{"loc":{"start":{"line":196,"column":23},"end":{"line":196,"column":null}},"type":"cond-expr","locations":[{"start":{"line":196,"column":43},"end":{"line":196,"column":54}},{"start":{"line":196,"column":54},"end":{"line":196,"column":null}}],"line":196},"23":{"loc":{"start":{"line":203,"column":23},"end":{"line":203,"column":null}},"type":"cond-expr","locations":[{"start":{"line":203,"column":42},"end":{"line":203,"column":53}},{"start":{"line":203,"column":53},"end":{"line":203,"column":null}}],"line":203}},"s":{"0":22,"1":22,"2":22,"3":22,"4":22,"5":22,"6":22,"7":22,"8":22,"9":22,"10":22,"11":22,"12":22,"13":11,"14":11,"15":0,"16":22,"17":22,"18":1,"19":1,"20":1,"21":22,"22":2,"23":22,"24":0,"25":22,"26":1,"27":1,"28":22,"29":9,"30":7,"31":0,"32":7,"33":7,"34":0,"35":7,"36":7,"37":7,"38":7,"39":1,"40":1,"41":22,"42":9,"43":13,"44":13,"45":12,"46":2,"47":2,"48":12,"49":0,"50":1,"51":4,"52":2,"53":0,"54":0},"f":{"0":22,"1":11,"2":1,"3":2,"4":0,"5":1,"6":9,"7":7,"8":7,"9":7,"10":1,"11":2,"12":2,"13":0,"14":1,"15":4,"16":2,"17":0,"18":0},"b":{"0":[22,22,22],"1":[0,11],"2":[22,21],"3":[21,0],"4":[21,21,21],"5":[1,0],"6":[1,0],"7":[0,7],"8":[0,7],"9":[9,13],"10":[1,12],"11":[12,0],"12":[0,12],"13":[0,12],"14":[12,0],"15":[22,12],"16":[0,4],"17":[2,10],"18":[22,2],"19":[1,1],"20":[0,2],"21":[2,0],"22":[12,0],"23":[0,12]},"meta":{"lastBranch":24,"lastFunction":19,"lastStatement":55,"seen":{"f:8:24:8:33":0,"s:19:24:19:Infinity":0,"s:20:22:20:Infinity":1,"s:21:28:21:Infinity":2,"s:22:24:22:Infinity":3,"s:23:40:23:Infinity":4,"s:24:38:24:Infinity":5,"s:25:36:25:Infinity":6,"s:26:8:26:Infinity":7,"s:27:21:27:Infinity":8,"s:28:8:28:Infinity":9,"s:29:26:29:Infinity":10,"s:31:25:31:Infinity":11,"b:31:25:31:56:31:56:31:104:31:104:31:Infinity":0,"s:33:2:38:Infinity":12,"f:33:12:33:18":1,"s:34:20:34:Infinity":13,"b:35:4:37:Infinity:undefined:undefined:undefined:undefined":1,"s:35:4:37:Infinity":14,"s:36:6:36:Infinity":15,"s:41:4:42:Infinity":16,"b:41:4:41:Infinity:42:5:42:Infinity":2,"b:42:56:42:67:42:67:42:Infinity":3,"b:42:5:42:34:42:34:42:45:42:45:42:56":4,"s:44:23:49:Infinity":17,"f:44:23:44:24":2,"s:45:4:45:Infinity":18,"b:46:4:48:Infinity:undefined:undefined:undefined:undefined":5,"s:46:4:48:Infinity":19,"s:47:6:47:Infinity":20,"s:51:22:53:Infinity":21,"f:51:22:51:28":3,"s:52:4:52:Infinity":22,"s:55:21:57:Infinity":23,"f:55:21:55:27":4,"s:56:4:56:Infinity":24,"s:59:26:63:Infinity":25,"f:59:26:59:32":5,"b:60:4:62:Infinity:undefined:undefined:undefined:undefined":6,"s:60:4:62:Infinity":26,"s:61:6:61:Infinity":27,"s:65:2:85:Infinity":28,"f:65:12:65:18":6,"s:66:4:84:Infinity":29,"f:67:34:67:35":7,"b:68:8:68:Infinity:undefined:undefined:undefined:undefined":7,"s:68:8:68:Infinity":30,"s:68:21:68:Infinity":31,"s:69:8:69:Infinity":32,"f:71:32:71:33":8,"b:72:8:72:Infinity:undefined:undefined:undefined:undefined":8,"s:72:8:72:Infinity":33,"s:72:21:72:Infinity":34,"s:73:8:73:Infinity":35,"f:76:12:76:13":9,"s:77:8:77:Infinity":36,"s:78:8:78:Infinity":37,"s:79:8:79:Infinity":38,"f:81:13:81:14":10,"s:82:8:82:Infinity":39,"s:83:8:83:Infinity":40,"b:87:2:87:Infinity:undefined:undefined:undefined:undefined":9,"s:87:2:87:Infinity":41,"s:87:15:87:Infinity":42,"b:88:2:88:Infinity:undefined:undefined:undefined:undefined":10,"s:88:2:88:Infinity":43,"s:88:13:88:Infinity":44,"s:90:23:92:Infinity":45,"f:90:23:90:29":11,"s:91:4:91:Infinity":46,"f:91:53:91:60":12,"s:91:60:91:95":47,"s:94:2:210:Infinity":48,"f:96:36:96:42":13,"s:96:42:96:68":49,"f:106:22:106:23":14,"s:106:29:106:Infinity":50,"b:115:79:115:90:115:90:115:94":11,"b:120:73:120:84:120:84:120:88":12,"b:125:81:125:92:125:92:125:96":13,"b:134:51:134:64:134:64:134:66":14,"b:136:9:136:Infinity:137:10:149:Infinity":15,"f:138:22:138:23":15,"s:139:14:147:Infinity":51,"b:142:65:142:76:142:76:142:78":16,"b:155:52:155:65:155:65:155:67":17,"b:157:9:157:Infinity:158:11:173:Infinity":18,"b:159:12:159:Infinity:161:12:173:Infinity":19,"f:162:25:162:26":16,"s:163:16:171:Infinity":52,"b:166:75:166:86:166:86:166:88":20,"b:169:21:169:35:169:35:169:Infinity":21,"f:195:21:195:27":17,"s:195:27:195:Infinity":53,"b:196:43:196:54:196:54:196:Infinity":22,"f:202:21:202:27":18,"s:202:27:202:Infinity":54,"b:203:42:203:53:203:53:203:Infinity":23}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedListVariants.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedListVariants.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.tsx": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.tsx","statementMap":{"0":{"start":{"line":8,"column":30},"end":{"line":8,"column":null}},"1":{"start":{"line":9,"column":30},"end":{"line":9,"column":null}},"2":{"start":{"line":10,"column":24},"end":{"line":10,"column":null}},"3":{"start":{"line":11,"column":8},"end":{"line":11,"column":null}},"4":{"start":{"line":13,"column":23},"end":{"line":37,"column":null}},"5":{"start":{"line":14,"column":4},"end":{"line":14,"column":null}},"6":{"start":{"line":15,"column":4},"end":{"line":15,"column":null}},"7":{"start":{"line":17,"column":4},"end":{"line":36,"column":null}},"8":{"start":{"line":19,"column":21},"end":{"line":19,"column":null}},"9":{"start":{"line":20,"column":6},"end":{"line":20,"column":null}},"10":{"start":{"line":21,"column":6},"end":{"line":21,"column":null}},"11":{"start":{"line":23,"column":18},"end":{"line":26,"column":null}},"12":{"start":{"line":28,"column":6},"end":{"line":33,"column":null}},"13":{"start":{"line":29,"column":8},"end":{"line":29,"column":null}},"14":{"start":{"line":31,"column":21},"end":{"line":31,"column":null}},"15":{"start":{"line":32,"column":8},"end":{"line":32,"column":null}},"16":{"start":{"line":35,"column":6},"end":{"line":35,"column":null}},"17":{"start":{"line":39,"column":2},"end":{"line":65,"column":null}},"18":{"start":{"line":49,"column":29},"end":{"line":49,"column":null}},"19":{"start":{"line":58,"column":29},"end":{"line":58,"column":null}}},"fnMap":{"0":{"name":"Login","decl":{"start":{"line":7,"column":24},"end":{"line":7,"column":32}},"loc":{"start":{"line":7,"column":32},"end":{"line":67,"column":null}},"line":7},"1":{"name":"(anonymous_1)","decl":{"start":{"line":13,"column":23},"end":{"line":13,"column":30}},"loc":{"start":{"line":13,"column":47},"end":{"line":37,"column":null}},"line":13},"2":{"name":"(anonymous_2)","decl":{"start":{"line":49,"column":22},"end":{"line":49,"column":23}},"loc":{"start":{"line":49,"column":29},"end":{"line":49,"column":null}},"line":49},"3":{"name":"(anonymous_3)","decl":{"start":{"line":58,"column":22},"end":{"line":58,"column":23}},"loc":{"start":{"line":58,"column":29},"end":{"line":58,"column":null}},"line":58}},"branchMap":{"0":{"loc":{"start":{"line":28,"column":6},"end":{"line":33,"column":null}},"type":"if","locations":[{"start":{"line":28,"column":6},"end":{"line":33,"column":null}},{"start":{"line":30,"column":13},"end":{"line":33,"column":null}}],"line":28},"1":{"loc":{"start":{"line":32,"column":17},"end":{"line":32,"column":47}},"type":"binary-expr","locations":[{"start":{"line":32,"column":17},"end":{"line":32,"column":33}},{"start":{"line":32,"column":33},"end":{"line":32,"column":47}}],"line":32},"2":{"loc":{"start":{"line":62,"column":9},"end":{"line":62,"column":null}},"type":"binary-expr","locations":[{"start":{"line":62,"column":9},"end":{"line":62,"column":18}},{"start":{"line":62,"column":18},"end":{"line":62,"column":null}}],"line":62}},"s":{"0":17,"1":17,"2":17,"3":17,"4":17,"5":3,"6":3,"7":3,"8":3,"9":3,"10":3,"11":3,"12":2,"13":1,"14":1,"15":1,"16":1,"17":17,"18":3,"19":3},"f":{"0":17,"1":3,"2":3,"3":3},"b":{"0":[1,1],"1":[1,0],"2":[17,2]},"meta":{"lastBranch":3,"lastFunction":4,"lastStatement":20,"seen":{"f:7:24:7:32":0,"s:8:30:8:Infinity":0,"s:9:30:9:Infinity":1,"s:10:24:10:Infinity":2,"s:11:8:11:Infinity":3,"s:13:23:37:Infinity":4,"f:13:23:13:30":1,"s:14:4:14:Infinity":5,"s:15:4:15:Infinity":6,"s:17:4:36:Infinity":7,"s:19:21:19:Infinity":8,"s:20:6:20:Infinity":9,"s:21:6:21:Infinity":10,"s:23:18:26:Infinity":11,"b:28:6:33:Infinity:30:13:33:Infinity":0,"s:28:6:33:Infinity":12,"s:29:8:29:Infinity":13,"s:31:21:31:Infinity":14,"s:32:8:32:Infinity":15,"b:32:17:32:33:32:33:32:47":1,"s:35:6:35:Infinity":16,"s:39:2:65:Infinity":17,"f:49:22:49:23":2,"s:49:29:49:Infinity":18,"f:58:22:58:23":3,"s:58:29:58:Infinity":19,"b:62:9:62:18:62:18:62:Infinity":2}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}}
-,"/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.tsx": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.tsx","statementMap":{"0":{"start":{"line":12,"column":24},"end":{"line":12,"column":null}},"1":{"start":{"line":14,"column":34},"end":{"line":14,"column":null}},"2":{"start":{"line":15,"column":28},"end":{"line":15,"column":null}},"3":{"start":{"line":16,"column":24},"end":{"line":16,"column":null}},"4":{"start":{"line":18,"column":34},"end":{"line":18,"column":null}},"5":{"start":{"line":21,"column":21},"end":{"line":36,"column":null}},"6":{"start":{"line":22,"column":4},"end":{"line":22,"column":null}},"7":{"start":{"line":23,"column":4},"end":{"line":35,"column":null}},"8":{"start":{"line":25,"column":8},"end":{"line":25,"column":null}},"9":{"start":{"line":25,"column":21},"end":{"line":25,"column":null}},"10":{"start":{"line":26,"column":8},"end":{"line":26,"column":null}},"11":{"start":{"line":29,"column":8},"end":{"line":29,"column":null}},"12":{"start":{"line":30,"column":8},"end":{"line":30,"column":null}},"13":{"start":{"line":33,"column":8},"end":{"line":33,"column":null}},"14":{"start":{"line":34,"column":8},"end":{"line":34,"column":null}},"15":{"start":{"line":38,"column":2},"end":{"line":41,"column":null}},"16":{"start":{"line":40,"column":4},"end":{"line":40,"column":null}},"17":{"start":{"line":44,"column":24},"end":{"line":66,"column":null}},"18":{"start":{"line":45,"column":4},"end":{"line":45,"column":null}},"19":{"start":{"line":46,"column":4},"end":{"line":46,"column":null}},"20":{"start":{"line":46,"column":21},"end":{"line":46,"column":null}},"21":{"start":{"line":48,"column":4},"end":{"line":48,"column":null}},"22":{"start":{"line":49,"column":4},"end":{"line":65,"column":null}},"23":{"start":{"line":55,"column":8},"end":{"line":55,"column":null}},"24":{"start":{"line":55,"column":21},"end":{"line":55,"column":null}},"25":{"start":{"line":56,"column":8},"end":{"line":56,"column":null}},"26":{"start":{"line":59,"column":8},"end":{"line":59,"column":null}},"27":{"start":{"line":60,"column":8},"end":{"line":60,"column":null}},"28":{"start":{"line":63,"column":8},"end":{"line":63,"column":null}},"29":{"start":{"line":64,"column":8},"end":{"line":64,"column":null}},"30":{"start":{"line":68,"column":27},"end":{"line":84,"column":null}},"31":{"start":{"line":69,"column":4},"end":{"line":69,"column":null}},"32":{"start":{"line":69,"column":75},"end":{"line":69,"column":null}},"33":{"start":{"line":71,"column":4},"end":{"line":71,"column":null}},"34":{"start":{"line":72,"column":4},"end":{"line":83,"column":null}},"35":{"start":{"line":76,"column":8},"end":{"line":76,"column":null}},"36":{"start":{"line":76,"column":21},"end":{"line":76,"column":null}},"37":{"start":{"line":77,"column":8},"end":{"line":77,"column":null}},"38":{"start":{"line":77,"column":37},"end":{"line":77,"column":49}},"39":{"start":{"line":78,"column":8},"end":{"line":78,"column":null}},"40":{"start":{"line":81,"column":8},"end":{"line":81,"column":null}},"41":{"start":{"line":82,"column":8},"end":{"line":82,"column":null}},"42":{"start":{"line":86,"column":23},"end":{"line":112,"column":null}},"43":{"start":{"line":87,"column":4},"end":{"line":87,"column":null}},"44":{"start":{"line":88,"column":4},"end":{"line":88,"column":null}},"45":{"start":{"line":88,"column":21},"end":{"line":88,"column":null}},"46":{"start":{"line":90,"column":4},"end":{"line":90,"column":null}},"47":{"start":{"line":91,"column":21},"end":{"line":91,"column":null}},"48":{"start":{"line":92,"column":4},"end":{"line":92,"column":null}},"49":{"start":{"line":93,"column":4},"end":{"line":93,"column":null}},"50":{"start":{"line":95,"column":4},"end":{"line":111,"column":null}},"51":{"start":{"line":100,"column":8},"end":{"line":100,"column":null}},"52":{"start":{"line":100,"column":21},"end":{"line":100,"column":null}},"53":{"start":{"line":101,"column":8},"end":{"line":101,"column":null}},"54":{"start":{"line":104,"column":8},"end":{"line":104,"column":null}},"55":{"start":{"line":105,"column":8},"end":{"line":105,"column":null}},"56":{"start":{"line":106,"column":8},"end":{"line":106,"column":null}},"57":{"start":{"line":109,"column":8},"end":{"line":109,"column":null}},"58":{"start":{"line":110,"column":8},"end":{"line":110,"column":null}},"59":{"start":{"line":114,"column":22},"end":{"line":131,"column":null}},"60":{"start":{"line":115,"column":4},"end":{"line":115,"column":null}},"61":{"start":{"line":116,"column":4},"end":{"line":130,"column":null}},"62":{"start":{"line":120,"column":8},"end":{"line":120,"column":null}},"63":{"start":{"line":120,"column":21},"end":{"line":120,"column":null}},"64":{"start":{"line":121,"column":8},"end":{"line":121,"column":null}},"65":{"start":{"line":124,"column":8},"end":{"line":124,"column":null}},"66":{"start":{"line":125,"column":8},"end":{"line":125,"column":null}},"67":{"start":{"line":128,"column":8},"end":{"line":128,"column":null}},"68":{"start":{"line":129,"column":8},"end":{"line":129,"column":null}},"69":{"start":{"line":133,"column":2},"end":{"line":234,"column":null}},"70":{"start":{"line":145,"column":31},"end":{"line":145,"column":null}},"71":{"start":{"line":163,"column":29},"end":{"line":163,"column":null}},"72":{"start":{"line":183,"column":31},"end":{"line":183,"column":null}},"73":{"start":{"line":217,"column":12},"end":{"line":230,"column":null}},"74":{"start":{"line":223,"column":31},"end":{"line":223,"column":null}}},"fnMap":{"0":{"name":"Settings","decl":{"start":{"line":11,"column":24},"end":{"line":11,"column":33}},"loc":{"start":{"line":11,"column":77},"end":{"line":236,"column":null}},"line":11},"1":{"name":"(anonymous_1)","decl":{"start":{"line":21,"column":39},"end":{"line":21,"column":45}},"loc":{"start":{"line":21,"column":45},"end":{"line":36,"column":5}},"line":21},"2":{"name":"(anonymous_2)","decl":{"start":{"line":24,"column":12},"end":{"line":24,"column":13}},"loc":{"start":{"line":24,"column":21},"end":{"line":27,"column":7}},"line":24},"3":{"name":"(anonymous_3)","decl":{"start":{"line":28,"column":12},"end":{"line":28,"column":13}},"loc":{"start":{"line":28,"column":22},"end":{"line":31,"column":7}},"line":28},"4":{"name":"(anonymous_4)","decl":{"start":{"line":32,"column":13},"end":{"line":32,"column":14}},"loc":{"start":{"line":32,"column":22},"end":{"line":35,"column":7}},"line":32},"5":{"name":"(anonymous_5)","decl":{"start":{"line":38,"column":12},"end":{"line":38,"column":18}},"loc":{"start":{"line":38,"column":18},"end":{"line":41,"column":5}},"line":38},"6":{"name":"(anonymous_6)","decl":{"start":{"line":44,"column":24},"end":{"line":44,"column":25}},"loc":{"start":{"line":44,"column":48},"end":{"line":66,"column":null}},"line":44},"7":{"name":"(anonymous_7)","decl":{"start":{"line":54,"column":12},"end":{"line":54,"column":13}},"loc":{"start":{"line":54,"column":21},"end":{"line":57,"column":7}},"line":54},"8":{"name":"(anonymous_8)","decl":{"start":{"line":58,"column":12},"end":{"line":58,"column":18}},"loc":{"start":{"line":58,"column":18},"end":{"line":61,"column":7}},"line":58},"9":{"name":"(anonymous_9)","decl":{"start":{"line":62,"column":13},"end":{"line":62,"column":14}},"loc":{"start":{"line":62,"column":22},"end":{"line":65,"column":7}},"line":62},"10":{"name":"(anonymous_10)","decl":{"start":{"line":68,"column":27},"end":{"line":68,"column":28}},"loc":{"start":{"line":68,"column":43},"end":{"line":84,"column":null}},"line":68},"11":{"name":"(anonymous_11)","decl":{"start":{"line":75,"column":12},"end":{"line":75,"column":13}},"loc":{"start":{"line":75,"column":21},"end":{"line":79,"column":7}},"line":75},"12":{"name":"(anonymous_12)","decl":{"start":{"line":77,"column":30},"end":{"line":77,"column":31}},"loc":{"start":{"line":77,"column":37},"end":{"line":77,"column":49}},"line":77},"13":{"name":"(anonymous_13)","decl":{"start":{"line":80,"column":13},"end":{"line":80,"column":14}},"loc":{"start":{"line":80,"column":22},"end":{"line":83,"column":7}},"line":80},"14":{"name":"(anonymous_14)","decl":{"start":{"line":86,"column":23},"end":{"line":86,"column":24}},"loc":{"start":{"line":86,"column":47},"end":{"line":112,"column":null}},"line":86},"15":{"name":"(anonymous_15)","decl":{"start":{"line":99,"column":12},"end":{"line":99,"column":13}},"loc":{"start":{"line":99,"column":21},"end":{"line":102,"column":7}},"line":99},"16":{"name":"(anonymous_16)","decl":{"start":{"line":103,"column":12},"end":{"line":103,"column":18}},"loc":{"start":{"line":103,"column":18},"end":{"line":107,"column":7}},"line":103},"17":{"name":"(anonymous_17)","decl":{"start":{"line":108,"column":13},"end":{"line":108,"column":14}},"loc":{"start":{"line":108,"column":22},"end":{"line":111,"column":7}},"line":108},"18":{"name":"(anonymous_18)","decl":{"start":{"line":114,"column":22},"end":{"line":114,"column":28}},"loc":{"start":{"line":114,"column":28},"end":{"line":131,"column":null}},"line":114},"19":{"name":"(anonymous_19)","decl":{"start":{"line":119,"column":12},"end":{"line":119,"column":13}},"loc":{"start":{"line":119,"column":21},"end":{"line":122,"column":7}},"line":119},"20":{"name":"(anonymous_20)","decl":{"start":{"line":123,"column":12},"end":{"line":123,"column":18}},"loc":{"start":{"line":123,"column":18},"end":{"line":126,"column":7}},"line":123},"21":{"name":"(anonymous_21)","decl":{"start":{"line":127,"column":13},"end":{"line":127,"column":14}},"loc":{"start":{"line":127,"column":22},"end":{"line":130,"column":7}},"line":127},"22":{"name":"(anonymous_22)","decl":{"start":{"line":145,"column":24},"end":{"line":145,"column":25}},"loc":{"start":{"line":145,"column":31},"end":{"line":145,"column":null}},"line":145},"23":{"name":"(anonymous_23)","decl":{"start":{"line":163,"column":22},"end":{"line":163,"column":23}},"loc":{"start":{"line":163,"column":29},"end":{"line":163,"column":null}},"line":163},"24":{"name":"(anonymous_24)","decl":{"start":{"line":183,"column":24},"end":{"line":183,"column":25}},"loc":{"start":{"line":183,"column":31},"end":{"line":183,"column":null}},"line":183},"25":{"name":"(anonymous_25)","decl":{"start":{"line":216,"column":21},"end":{"line":216,"column":22}},"loc":{"start":{"line":217,"column":12},"end":{"line":230,"column":null}},"line":217},"26":{"name":"(anonymous_26)","decl":{"start":{"line":223,"column":25},"end":{"line":223,"column":31}},"loc":{"start":{"line":223,"column":31},"end":{"line":223,"column":null}},"line":223}},"branchMap":{"0":{"loc":{"start":{"line":25,"column":8},"end":{"line":25,"column":null}},"type":"if","locations":[{"start":{"line":25,"column":8},"end":{"line":25,"column":null}},{"start":{},"end":{}}],"line":25},"1":{"loc":{"start":{"line":46,"column":4},"end":{"line":46,"column":null}},"type":"if","locations":[{"start":{"line":46,"column":4},"end":{"line":46,"column":null}},{"start":{},"end":{}}],"line":46},"2":{"loc":{"start":{"line":55,"column":8},"end":{"line":55,"column":null}},"type":"if","locations":[{"start":{"line":55,"column":8},"end":{"line":55,"column":null}},{"start":{},"end":{}}],"line":55},"3":{"loc":{"start":{"line":69,"column":4},"end":{"line":69,"column":null}},"type":"if","locations":[{"start":{"line":69,"column":4},"end":{"line":69,"column":null}},{"start":{},"end":{}}],"line":69},"4":{"loc":{"start":{"line":76,"column":8},"end":{"line":76,"column":null}},"type":"if","locations":[{"start":{"line":76,"column":8},"end":{"line":76,"column":null}},{"start":{},"end":{}}],"line":76},"5":{"loc":{"start":{"line":88,"column":4},"end":{"line":88,"column":null}},"type":"if","locations":[{"start":{"line":88,"column":4},"end":{"line":88,"column":null}},{"start":{},"end":{}}],"line":88},"6":{"loc":{"start":{"line":100,"column":8},"end":{"line":100,"column":null}},"type":"if","locations":[{"start":{"line":100,"column":8},"end":{"line":100,"column":null}},{"start":{},"end":{}}],"line":100},"7":{"loc":{"start":{"line":120,"column":8},"end":{"line":120,"column":null}},"type":"if","locations":[{"start":{"line":120,"column":8},"end":{"line":120,"column":null}},{"start":{},"end":{}}],"line":120},"8":{"loc":{"start":{"line":137,"column":7},"end":{"line":154,"column":null}},"type":"binary-expr","locations":[{"start":{"line":137,"column":7},"end":{"line":137,"column":null}},{"start":{"line":138,"column":8},"end":{"line":154,"column":null}}],"line":137},"9":{"loc":{"start":{"line":144,"column":21},"end":{"line":144,"column":null}},"type":"binary-expr","locations":[{"start":{"line":144,"column":21},"end":{"line":144,"column":34}},{"start":{"line":144,"column":34},"end":{"line":144,"column":null}}],"line":144},"10":{"loc":{"start":{"line":183,"column":45},"end":{"line":183,"column":72}},"type":"binary-expr","locations":[{"start":{"line":183,"column":45},"end":{"line":183,"column":68}},{"start":{"line":183,"column":68},"end":{"line":183,"column":72}}],"line":183},"11":{"loc":{"start":{"line":187,"column":44},"end":{"line":187,"column":68}},"type":"binary-expr","locations":[{"start":{"line":187,"column":44},"end":{"line":187,"column":59}},{"start":{"line":187,"column":59},"end":{"line":187,"column":68}}],"line":187},"12":{"loc":{"start":{"line":210,"column":7},"end":{"line":210,"column":null}},"type":"binary-expr","locations":[{"start":{"line":210,"column":7},"end":{"line":210,"column":16}},{"start":{"line":210,"column":16},"end":{"line":210,"column":null}}],"line":210},"13":{"loc":{"start":{"line":214,"column":9},"end":{"line":214,"column":null}},"type":"binary-expr","locations":[{"start":{"line":214,"column":9},"end":{"line":214,"column":20}},{"start":{"line":214,"column":20},"end":{"line":214,"column":null}}],"line":214},"14":{"loc":{"start":{"line":219,"column":46},"end":{"line":219,"column":73}},"type":"binary-expr","locations":[{"start":{"line":219,"column":46},"end":{"line":219,"column":60}},{"start":{"line":219,"column":60},"end":{"line":219,"column":73}}],"line":219}},"s":{"0":34,"1":34,"2":34,"3":34,"4":34,"5":34,"6":9,"7":9,"8":9,"9":0,"10":9,"11":9,"12":9,"13":0,"14":0,"15":34,"16":7,"17":34,"18":2,"19":2,"20":0,"21":2,"22":2,"23":2,"24":1,"25":1,"26":1,"27":1,"28":1,"29":1,"30":34,"31":1,"32":0,"33":1,"34":1,"35":1,"36":0,"37":1,"38":1,"39":1,"40":0,"41":0,"42":34,"43":1,"44":1,"45":0,"46":1,"47":1,"48":1,"49":1,"50":1,"51":1,"52":0,"53":1,"54":1,"55":1,"56":1,"57":0,"58":0,"59":34,"60":1,"61":1,"62":1,"63":0,"64":1,"65":1,"66":1,"67":0,"68":0,"69":34,"70":1,"71":2,"72":1,"73":6,"74":1},"f":{"0":34,"1":9,"2":9,"3":9,"4":0,"5":7,"6":2,"7":2,"8":1,"9":1,"10":1,"11":1,"12":1,"13":0,"14":1,"15":1,"16":1,"17":0,"18":1,"19":1,"20":1,"21":0,"22":1,"23":2,"24":1,"25":6,"26":1},"b":{"0":[0,9],"1":[0,2],"2":[1,1],"3":[0,1],"4":[0,1],"5":[0,1],"6":[0,1],"7":[0,1],"8":[34,3],"9":[3,0],"10":[1,0],"11":[34,3],"12":[34,1],"13":[34,13],"14":[6,0]},"meta":{"lastBranch":15,"lastFunction":27,"lastStatement":75,"seen":{"f:11:24:11:33":0,"s:12:24:12:Infinity":0,"s:14:34:14:Infinity":1,"s:15:28:15:Infinity":2,"s:16:24:16:Infinity":3,"s:18:34:18:Infinity":4,"s:21:21:36:Infinity":5,"f:21:39:21:45":1,"s:22:4:22:Infinity":6,"s:23:4:35:Infinity":7,"f:24:12:24:13":2,"b:25:8:25:Infinity:undefined:undefined:undefined:undefined":0,"s:25:8:25:Infinity":8,"s:25:21:25:Infinity":9,"s:26:8:26:Infinity":10,"f:28:12:28:13":3,"s:29:8:29:Infinity":11,"s:30:8:30:Infinity":12,"f:32:13:32:14":4,"s:33:8:33:Infinity":13,"s:34:8:34:Infinity":14,"s:38:2:41:Infinity":15,"f:38:12:38:18":5,"s:40:4:40:Infinity":16,"s:44:24:66:Infinity":17,"f:44:24:44:25":6,"s:45:4:45:Infinity":18,"b:46:4:46:Infinity:undefined:undefined:undefined:undefined":1,"s:46:4:46:Infinity":19,"s:46:21:46:Infinity":20,"s:48:4:48:Infinity":21,"s:49:4:65:Infinity":22,"f:54:12:54:13":7,"b:55:8:55:Infinity:undefined:undefined:undefined:undefined":2,"s:55:8:55:Infinity":23,"s:55:21:55:Infinity":24,"s:56:8:56:Infinity":25,"f:58:12:58:18":8,"s:59:8:59:Infinity":26,"s:60:8:60:Infinity":27,"f:62:13:62:14":9,"s:63:8:63:Infinity":28,"s:64:8:64:Infinity":29,"s:68:27:84:Infinity":30,"f:68:27:68:28":10,"b:69:4:69:Infinity:undefined:undefined:undefined:undefined":3,"s:69:4:69:Infinity":31,"s:69:75:69:Infinity":32,"s:71:4:71:Infinity":33,"s:72:4:83:Infinity":34,"f:75:12:75:13":11,"b:76:8:76:Infinity:undefined:undefined:undefined:undefined":4,"s:76:8:76:Infinity":35,"s:76:21:76:Infinity":36,"s:77:8:77:Infinity":37,"f:77:30:77:31":12,"s:77:37:77:49":38,"s:78:8:78:Infinity":39,"f:80:13:80:14":13,"s:81:8:81:Infinity":40,"s:82:8:82:Infinity":41,"s:86:23:112:Infinity":42,"f:86:23:86:24":14,"s:87:4:87:Infinity":43,"b:88:4:88:Infinity:undefined:undefined:undefined:undefined":5,"s:88:4:88:Infinity":44,"s:88:21:88:Infinity":45,"s:90:4:90:Infinity":46,"s:91:21:91:Infinity":47,"s:92:4:92:Infinity":48,"s:93:4:93:Infinity":49,"s:95:4:111:Infinity":50,"f:99:12:99:13":15,"b:100:8:100:Infinity:undefined:undefined:undefined:undefined":6,"s:100:8:100:Infinity":51,"s:100:21:100:Infinity":52,"s:101:8:101:Infinity":53,"f:103:12:103:18":16,"s:104:8:104:Infinity":54,"s:105:8:105:Infinity":55,"s:106:8:106:Infinity":56,"f:108:13:108:14":17,"s:109:8:109:Infinity":57,"s:110:8:110:Infinity":58,"s:114:22:131:Infinity":59,"f:114:22:114:28":18,"s:115:4:115:Infinity":60,"s:116:4:130:Infinity":61,"f:119:12:119:13":19,"b:120:8:120:Infinity:undefined:undefined:undefined:undefined":7,"s:120:8:120:Infinity":62,"s:120:21:120:Infinity":63,"s:121:8:121:Infinity":64,"f:123:12:123:18":20,"s:124:8:124:Infinity":65,"s:125:8:125:Infinity":66,"f:127:13:127:14":21,"s:128:8:128:Infinity":67,"s:129:8:129:Infinity":68,"s:133:2:234:Infinity":69,"b:137:7:137:Infinity:138:8:154:Infinity":8,"b:144:21:144:34:144:34:144:Infinity":9,"f:145:24:145:25":22,"s:145:31:145:Infinity":70,"f:163:22:163:23":23,"s:163:29:163:Infinity":71,"f:183:24:183:25":24,"s:183:31:183:Infinity":72,"b:183:45:183:68:183:68:183:72":10,"b:187:44:187:59:187:59:187:68":11,"b:210:7:210:16:210:16:210:Infinity":12,"b:214:9:214:20:214:20:214:Infinity":13,"f:216:21:216:22":25,"s:217:12:230:Infinity":73,"b:219:46:219:60:219:60:219:73":14,"f:223:25:223:31":26,"s:223:31:223:Infinity":74}}}
-}
diff --git a/frontend/coverage/favicon.png b/frontend/coverage/favicon.png
deleted file mode 100644
index c1525b8..0000000
--- a/frontend/coverage/favicon.png
+++ /dev/null
Binary files differ
diff --git a/frontend/coverage/index.html b/frontend/coverage/index.html
deleted file mode 100644
index 3c8ce1b..0000000
--- a/frontend/coverage/index.html
+++ /dev/null
@@ -1,131 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for All files</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="prettify.css" />
- <link rel="stylesheet" href="base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1>All files</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">84.12% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>302/359</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">73.24% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>167/228</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">81.48% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>88/108</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">86.4% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>286/331</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line high'></div>
- <div class="pad1">
-<table class="coverage-summary">
-<thead>
-<tr>
- <th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
- <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
- <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
- <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
- <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
- <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
- <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
- <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
- <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
- <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
-</tr>
-</thead>
-<tbody><tr>
- <td class="file medium" data-value="src"><a href="src/index.html">src</a></td>
- <td data-value="70.21" class="pic medium">
- <div class="chart"><div class="cover-fill" style="width: 70%"></div><div class="cover-empty" style="width: 30%"></div></div>
- </td>
- <td data-value="70.21" class="pct medium">70.21%</td>
- <td data-value="47" class="abs medium">33/47</td>
- <td data-value="65.71" class="pct medium">65.71%</td>
- <td data-value="35" class="abs medium">23/35</td>
- <td data-value="60" class="pct medium">60%</td>
- <td data-value="15" class="abs medium">9/15</td>
- <td data-value="71.11" class="pct medium">71.11%</td>
- <td data-value="45" class="abs medium">32/45</td>
- </tr>
-
-<tr>
- <td class="file high" data-value="src/components"><a href="src/components/index.html">src/components</a></td>
- <td data-value="86.21" class="pic high">
- <div class="chart"><div class="cover-fill" style="width: 86%"></div><div class="cover-empty" style="width: 14%"></div></div>
- </td>
- <td data-value="86.21" class="pct high">86.21%</td>
- <td data-value="312" class="abs high">269/312</td>
- <td data-value="74.61" class="pct medium">74.61%</td>
- <td data-value="193" class="abs medium">144/193</td>
- <td data-value="84.94" class="pct high">84.94%</td>
- <td data-value="93" class="abs high">79/93</td>
- <td data-value="88.81" class="pct high">88.81%</td>
- <td data-value="286" class="abs high">254/286</td>
- </tr>
-
-</tbody>
-</table>
-</div>
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="sorter.js"></script>
- <script src="block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/prettify.css b/frontend/coverage/prettify.css
deleted file mode 100644
index b317a7c..0000000
--- a/frontend/coverage/prettify.css
+++ /dev/null
@@ -1 +0,0 @@
-.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
diff --git a/frontend/coverage/prettify.js b/frontend/coverage/prettify.js
deleted file mode 100644
index b322523..0000000
--- a/frontend/coverage/prettify.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/* eslint-disable */
-window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V<U;++V){var ae=Z[V];if(ae.ignoreCase){ac=true}else{if(/[a-z]/i.test(ae.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi,""))){S=true;ac=false;break}}}var Y={b:8,t:9,n:10,v:11,f:12,r:13};function ab(ah){var ag=ah.charCodeAt(0);if(ag!==92){return ag}var af=ah.charAt(1);ag=Y[af];if(ag){return ag}else{if("0"<=af&&af<="7"){return parseInt(ah.substring(1),8)}else{if(af==="u"||af==="x"){return parseInt(ah.substring(2),16)}else{return ah.charCodeAt(1)}}}}function T(af){if(af<32){return(af<16?"\\x0":"\\x")+af.toString(16)}var ag=String.fromCharCode(af);if(ag==="\\"||ag==="-"||ag==="["||ag==="]"){ag="\\"+ag}return ag}function X(am){var aq=am.substring(1,am.length-1).match(new RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));var ak=[];var af=[];var ao=aq[0]==="^";for(var ar=ao?1:0,aj=aq.length;ar<aj;++ar){var ah=aq[ar];if(/\\[bdsw]/i.test(ah)){ak.push(ah)}else{var ag=ab(ah);var al;if(ar+2<aj&&"-"===aq[ar+1]){al=ab(aq[ar+2]);ar+=2}else{al=ag}af.push([ag,al]);if(!(al<65||ag>122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;ar<af.length;++ar){var at=af[ar];if(at[0]<=ap[1]+1){ap[1]=Math.max(ap[1],at[1])}else{ai.push(ap=at)}}var an=["["];if(ao){an.push("^")}an.push.apply(an,ak);for(var ar=0;ar<ai.length;++ar){var at=ai[ar];an.push(T(at[0]));if(at[1]>at[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak<ah;++ak){var ag=aj[ak];if(ag==="("){++am}else{if("\\"===ag.charAt(0)){var af=+ag.substring(1);if(af&&af<=am){an[af]=-1}}}}for(var ak=1;ak<an.length;++ak){if(-1===an[ak]){an[ak]=++ad}}for(var ak=0,am=0;ak<ah;++ak){var ag=aj[ak];if(ag==="("){++am;if(an[am]===undefined){aj[ak]="(?:"}}else{if("\\"===ag.charAt(0)){var af=+ag.substring(1);if(af&&af<=am){aj[ak]="\\"+an[am]}}}}for(var ak=0,am=0;ak<ah;++ak){if("^"===aj[ak]&&"^"!==aj[ak+1]){aj[ak]=""}}if(al.ignoreCase&&S){for(var ak=0;ak<ah;++ak){var ag=aj[ak];var ai=ag.charAt(0);if(ag.length>=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V<U;++V){var ae=Z[V];if(ae.global||ae.multiline){throw new Error(""+ae)}aa.push("(?:"+W(ae)+")")}return new RegExp(aa.join("|"),ac?"gi":"g")}function a(V){var U=/(?:^|\s)nocode(?:\s|$)/;var X=[];var T=0;var Z=[];var W=0;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=document.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Y=S&&"pre"===S.substring(0,3);function aa(ab){switch(ab.nodeType){case 1:if(U.test(ab.className)){return}for(var ae=ab.firstChild;ae;ae=ae.nextSibling){aa(ae)}var ad=ab.nodeName;if("BR"===ad||"LI"===ad){X[W]="\n";Z[W<<1]=T++;Z[(W++<<1)|1]=ab}break;case 3:case 4:var ac=ab.nodeValue;if(ac.length){if(!Y){ac=ac.replace(/[ \t\r\n]+/g," ")}else{ac=ac.replace(/\r\n?/g,"\n")}X[W]=ac;Z[W<<1]=T;T+=ac.length;Z[(W++<<1)|1]=ab}break}}aa(V);return{sourceCode:X.join("").replace(/\n$/,""),spans:Z}}function B(S,U,W,T){if(!U){return}var V={sourceCode:U,basePos:S};W(V);T.push.apply(T,V.decorations)}var v=/\S/;function o(S){var V=undefined;for(var U=S.firstChild;U;U=U.nextSibling){var T=U.nodeType;V=(T===1)?(V?S:U):(T===3)?(v.test(U.nodeValue)?S:V):V}return V===S?undefined:V}function g(U,T){var S={};var V;(function(){var ad=U.concat(T);var ah=[];var ag={};for(var ab=0,Z=ad.length;ab<Z;++ab){var Y=ad[ab];var ac=Y[3];if(ac){for(var ae=ac.length;--ae>=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae<aq;++ae){var ag=an[ae];var ap=aj[ag];var ai=void 0;var am;if(typeof ap==="string"){am=false}else{var aa=S[ag.charAt(0)];if(aa){ai=ag.match(aa[1]);ap=aa[0]}else{for(var ao=0;ao<X;++ao){aa=T[ao];ai=ag.match(aa[1]);if(ai){ap=aa[0];break}}if(!ai){ap=F}}am=ap.length>=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y<W.length;++Y){ae(W[Y])}if(ag===(ag|0)){W[0].setAttribute("value",ag)}var aa=ac.createElement("OL");aa.className="linenums";var X=Math.max(0,((ag-1))|0)||0;for(var Y=0,T=W.length;Y<T;++Y){af=W[Y];af.className="L"+((Y+X)%10);if(!af.firstChild){af.appendChild(ac.createTextNode("\xA0"))}aa.appendChild(af)}V.appendChild(aa)}function D(ac){var aj=/\bMSIE\b/.test(navigator.userAgent);var am=/\n/g;var al=ac.sourceCode;var an=al.length;var V=0;var aa=ac.spans;var T=aa.length;var ah=0;var X=ac.decorations;var Y=X.length;var Z=0;X[Y]=an;var ar,aq;for(aq=ar=0;aq<Y;){if(X[aq]!==X[aq+2]){X[ar++]=X[aq++];X[ar++]=X[aq++]}else{aq+=2}}Y=ar;for(aq=ar=0;aq<Y;){var at=X[aq];var ab=X[aq+1];var W=aq+2;while(W+2<=Y&&X[W+1]===ab){W+=2}X[ar++]=at;X[ar++]=ab;aq=W}Y=X.length=ar;var ae=null;while(ah<T){var af=aa[ah];var S=aa[ah+2]||an;var ag=X[Z];var ap=X[Z+2]||an;var W=Math.min(S,ap);var ak=aa[ah+1];var U;if(ak.nodeType!==1&&(U=al.substring(V,W))){if(aj){U=U.replace(am,"\r")}ak.nodeValue=U;var ai=ak.ownerDocument;var ao=ai.createElement("SPAN");ao.className=X[Z+1];var ad=ak.parentNode;ad.replaceChild(ao,ak);ao.appendChild(ak);if(V<S){aa[ah+1]=ak=ai.createTextNode(al.substring(W,S));ad.insertBefore(ak,ao.nextSibling)}}V=W;if(V>=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*</.test(S)?"default-markup":"default-code"}return t[T]}c(K,["default-code"]);c(g([],[[F,/^[^<?]+/],[E,/^<!\w[^>]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa<ac.length;++aa){for(var Z=0,V=ac[aa].length;Z<V;++Z){T.push(ac[aa][Z])}}ac=null;var W=Date;if(!W.now){W={now:function(){return +(new Date)}}}var X=0;var S;var ab=/\blang(?:uage)?-([\w.]+)(?!\S)/;var ae=/\bprettyprint\b/;function U(){var ag=(window.PR_SHOULD_USE_CONTINUATION?W.now()+250:Infinity);for(;X<T.length&&W.now()<ag;X++){var aj=T[X];var ai=aj.className;if(ai.indexOf("prettyprint")>=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X<T.length){setTimeout(U,250)}else{if(ad){ad()}}}U()}window.prettyPrintOne=y;window.prettyPrint=b;window.PR={createSimpleLexer:g,registerLangHandler:c,sourceDecorator:i,PR_ATTRIB_NAME:P,PR_ATTRIB_VALUE:n,PR_COMMENT:j,PR_DECLARATION:E,PR_KEYWORD:z,PR_LITERAL:G,PR_NOCODE:N,PR_PLAIN:F,PR_PUNCTUATION:L,PR_SOURCE:J,PR_STRING:C,PR_TAG:m,PR_TYPE:O}})();PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_DECLARATION,/^<!\w[^>]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^<script\b[^>]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:<!--|-->)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]);
diff --git a/frontend/coverage/sort-arrow-sprite.png b/frontend/coverage/sort-arrow-sprite.png
deleted file mode 100644
index 6ed6831..0000000
--- a/frontend/coverage/sort-arrow-sprite.png
+++ /dev/null
Binary files differ
diff --git a/frontend/coverage/sorter.js b/frontend/coverage/sorter.js
deleted file mode 100644
index 4ed70ae..0000000
--- a/frontend/coverage/sorter.js
+++ /dev/null
@@ -1,210 +0,0 @@
-/* eslint-disable */
-var addSorting = (function() {
- 'use strict';
- var cols,
- currentSort = {
- index: 0,
- desc: false
- };
-
- // returns the summary table element
- function getTable() {
- return document.querySelector('.coverage-summary');
- }
- // returns the thead element of the summary table
- function getTableHeader() {
- return getTable().querySelector('thead tr');
- }
- // returns the tbody element of the summary table
- function getTableBody() {
- return getTable().querySelector('tbody');
- }
- // returns the th element for nth column
- function getNthColumn(n) {
- return getTableHeader().querySelectorAll('th')[n];
- }
-
- function onFilterInput() {
- const searchValue = document.getElementById('fileSearch').value;
- const rows = document.getElementsByTagName('tbody')[0].children;
-
- // Try to create a RegExp from the searchValue. If it fails (invalid regex),
- // it will be treated as a plain text search
- let searchRegex;
- try {
- searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
- } catch (error) {
- searchRegex = null;
- }
-
- for (let i = 0; i < rows.length; i++) {
- const row = rows[i];
- let isMatch = false;
-
- if (searchRegex) {
- // If a valid regex was created, use it for matching
- isMatch = searchRegex.test(row.textContent);
- } else {
- // Otherwise, fall back to the original plain text search
- isMatch = row.textContent
- .toLowerCase()
- .includes(searchValue.toLowerCase());
- }
-
- row.style.display = isMatch ? '' : 'none';
- }
- }
-
- // loads the search box
- function addSearchBox() {
- var template = document.getElementById('filterTemplate');
- var templateClone = template.content.cloneNode(true);
- templateClone.getElementById('fileSearch').oninput = onFilterInput;
- template.parentElement.appendChild(templateClone);
- }
-
- // loads all columns
- function loadColumns() {
- var colNodes = getTableHeader().querySelectorAll('th'),
- colNode,
- cols = [],
- col,
- i;
-
- for (i = 0; i < colNodes.length; i += 1) {
- colNode = colNodes[i];
- col = {
- key: colNode.getAttribute('data-col'),
- sortable: !colNode.getAttribute('data-nosort'),
- type: colNode.getAttribute('data-type') || 'string'
- };
- cols.push(col);
- if (col.sortable) {
- col.defaultDescSort = col.type === 'number';
- colNode.innerHTML =
- colNode.innerHTML + '<span class="sorter"></span>';
- }
- }
- return cols;
- }
- // attaches a data attribute to every tr element with an object
- // of data values keyed by column name
- function loadRowData(tableRow) {
- var tableCols = tableRow.querySelectorAll('td'),
- colNode,
- col,
- data = {},
- i,
- val;
- for (i = 0; i < tableCols.length; i += 1) {
- colNode = tableCols[i];
- col = cols[i];
- val = colNode.getAttribute('data-value');
- if (col.type === 'number') {
- val = Number(val);
- }
- data[col.key] = val;
- }
- return data;
- }
- // loads all row data
- function loadData() {
- var rows = getTableBody().querySelectorAll('tr'),
- i;
-
- for (i = 0; i < rows.length; i += 1) {
- rows[i].data = loadRowData(rows[i]);
- }
- }
- // sorts the table using the data for the ith column
- function sortByIndex(index, desc) {
- var key = cols[index].key,
- sorter = function(a, b) {
- a = a.data[key];
- b = b.data[key];
- return a < b ? -1 : a > b ? 1 : 0;
- },
- finalSorter = sorter,
- tableBody = document.querySelector('.coverage-summary tbody'),
- rowNodes = tableBody.querySelectorAll('tr'),
- rows = [],
- i;
-
- if (desc) {
- finalSorter = function(a, b) {
- return -1 * sorter(a, b);
- };
- }
-
- for (i = 0; i < rowNodes.length; i += 1) {
- rows.push(rowNodes[i]);
- tableBody.removeChild(rowNodes[i]);
- }
-
- rows.sort(finalSorter);
-
- for (i = 0; i < rows.length; i += 1) {
- tableBody.appendChild(rows[i]);
- }
- }
- // removes sort indicators for current column being sorted
- function removeSortIndicators() {
- var col = getNthColumn(currentSort.index),
- cls = col.className;
-
- cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
- col.className = cls;
- }
- // adds sort indicators for current column being sorted
- function addSortIndicators() {
- getNthColumn(currentSort.index).className += currentSort.desc
- ? ' sorted-desc'
- : ' sorted';
- }
- // adds event listeners for all sorter widgets
- function enableUI() {
- var i,
- el,
- ithSorter = function ithSorter(i) {
- var col = cols[i];
-
- return function() {
- var desc = col.defaultDescSort;
-
- if (currentSort.index === i) {
- desc = !currentSort.desc;
- }
- sortByIndex(i, desc);
- removeSortIndicators();
- currentSort.index = i;
- currentSort.desc = desc;
- addSortIndicators();
- };
- };
- for (i = 0; i < cols.length; i += 1) {
- if (cols[i].sortable) {
- // add the click event handler on the th so users
- // dont have to click on those tiny arrows
- el = getNthColumn(i).querySelector('.sorter').parentElement;
- if (el.addEventListener) {
- el.addEventListener('click', ithSorter(i));
- } else {
- el.attachEvent('onclick', ithSorter(i));
- }
- }
- }
- }
- // adds sorting functionality to the UI
- return function() {
- if (!getTable()) {
- return;
- }
- cols = loadColumns();
- loadData();
- addSearchBox();
- addSortIndicators();
- enableUI();
- };
-})();
-
-window.addEventListener('load', addSorting);
diff --git a/frontend/coverage/src/App.css.html b/frontend/coverage/src/App.css.html
deleted file mode 100644
index d23e87b..0000000
--- a/frontend/coverage/src/App.css.html
+++ /dev/null
@@ -1,478 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/App.css</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../prettify.css" />
- <link rel="stylesheet" href="../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../index.html">All files</a> / <a href="index.html">src</a> App.css</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line low'></div>
- <pre><table class="coverage">
-<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
-<a name='L2'></a><a href='#L2'>2</a>
-<a name='L3'></a><a href='#L3'>3</a>
-<a name='L4'></a><a href='#L4'>4</a>
-<a name='L5'></a><a href='#L5'>5</a>
-<a name='L6'></a><a href='#L6'>6</a>
-<a name='L7'></a><a href='#L7'>7</a>
-<a name='L8'></a><a href='#L8'>8</a>
-<a name='L9'></a><a href='#L9'>9</a>
-<a name='L10'></a><a href='#L10'>10</a>
-<a name='L11'></a><a href='#L11'>11</a>
-<a name='L12'></a><a href='#L12'>12</a>
-<a name='L13'></a><a href='#L13'>13</a>
-<a name='L14'></a><a href='#L14'>14</a>
-<a name='L15'></a><a href='#L15'>15</a>
-<a name='L16'></a><a href='#L16'>16</a>
-<a name='L17'></a><a href='#L17'>17</a>
-<a name='L18'></a><a href='#L18'>18</a>
-<a name='L19'></a><a href='#L19'>19</a>
-<a name='L20'></a><a href='#L20'>20</a>
-<a name='L21'></a><a href='#L21'>21</a>
-<a name='L22'></a><a href='#L22'>22</a>
-<a name='L23'></a><a href='#L23'>23</a>
-<a name='L24'></a><a href='#L24'>24</a>
-<a name='L25'></a><a href='#L25'>25</a>
-<a name='L26'></a><a href='#L26'>26</a>
-<a name='L27'></a><a href='#L27'>27</a>
-<a name='L28'></a><a href='#L28'>28</a>
-<a name='L29'></a><a href='#L29'>29</a>
-<a name='L30'></a><a href='#L30'>30</a>
-<a name='L31'></a><a href='#L31'>31</a>
-<a name='L32'></a><a href='#L32'>32</a>
-<a name='L33'></a><a href='#L33'>33</a>
-<a name='L34'></a><a href='#L34'>34</a>
-<a name='L35'></a><a href='#L35'>35</a>
-<a name='L36'></a><a href='#L36'>36</a>
-<a name='L37'></a><a href='#L37'>37</a>
-<a name='L38'></a><a href='#L38'>38</a>
-<a name='L39'></a><a href='#L39'>39</a>
-<a name='L40'></a><a href='#L40'>40</a>
-<a name='L41'></a><a href='#L41'>41</a>
-<a name='L42'></a><a href='#L42'>42</a>
-<a name='L43'></a><a href='#L43'>43</a>
-<a name='L44'></a><a href='#L44'>44</a>
-<a name='L45'></a><a href='#L45'>45</a>
-<a name='L46'></a><a href='#L46'>46</a>
-<a name='L47'></a><a href='#L47'>47</a>
-<a name='L48'></a><a href='#L48'>48</a>
-<a name='L49'></a><a href='#L49'>49</a>
-<a name='L50'></a><a href='#L50'>50</a>
-<a name='L51'></a><a href='#L51'>51</a>
-<a name='L52'></a><a href='#L52'>52</a>
-<a name='L53'></a><a href='#L53'>53</a>
-<a name='L54'></a><a href='#L54'>54</a>
-<a name='L55'></a><a href='#L55'>55</a>
-<a name='L56'></a><a href='#L56'>56</a>
-<a name='L57'></a><a href='#L57'>57</a>
-<a name='L58'></a><a href='#L58'>58</a>
-<a name='L59'></a><a href='#L59'>59</a>
-<a name='L60'></a><a href='#L60'>60</a>
-<a name='L61'></a><a href='#L61'>61</a>
-<a name='L62'></a><a href='#L62'>62</a>
-<a name='L63'></a><a href='#L63'>63</a>
-<a name='L64'></a><a href='#L64'>64</a>
-<a name='L65'></a><a href='#L65'>65</a>
-<a name='L66'></a><a href='#L66'>66</a>
-<a name='L67'></a><a href='#L67'>67</a>
-<a name='L68'></a><a href='#L68'>68</a>
-<a name='L69'></a><a href='#L69'>69</a>
-<a name='L70'></a><a href='#L70'>70</a>
-<a name='L71'></a><a href='#L71'>71</a>
-<a name='L72'></a><a href='#L72'>72</a>
-<a name='L73'></a><a href='#L73'>73</a>
-<a name='L74'></a><a href='#L74'>74</a>
-<a name='L75'></a><a href='#L75'>75</a>
-<a name='L76'></a><a href='#L76'>76</a>
-<a name='L77'></a><a href='#L77'>77</a>
-<a name='L78'></a><a href='#L78'>78</a>
-<a name='L79'></a><a href='#L79'>79</a>
-<a name='L80'></a><a href='#L80'>80</a>
-<a name='L81'></a><a href='#L81'>81</a>
-<a name='L82'></a><a href='#L82'>82</a>
-<a name='L83'></a><a href='#L83'>83</a>
-<a name='L84'></a><a href='#L84'>84</a>
-<a name='L85'></a><a href='#L85'>85</a>
-<a name='L86'></a><a href='#L86'>86</a>
-<a name='L87'></a><a href='#L87'>87</a>
-<a name='L88'></a><a href='#L88'>88</a>
-<a name='L89'></a><a href='#L89'>89</a>
-<a name='L90'></a><a href='#L90'>90</a>
-<a name='L91'></a><a href='#L91'>91</a>
-<a name='L92'></a><a href='#L92'>92</a>
-<a name='L93'></a><a href='#L93'>93</a>
-<a name='L94'></a><a href='#L94'>94</a>
-<a name='L95'></a><a href='#L95'>95</a>
-<a name='L96'></a><a href='#L96'>96</a>
-<a name='L97'></a><a href='#L97'>97</a>
-<a name='L98'></a><a href='#L98'>98</a>
-<a name='L99'></a><a href='#L99'>99</a>
-<a name='L100'></a><a href='#L100'>100</a>
-<a name='L101'></a><a href='#L101'>101</a>
-<a name='L102'></a><a href='#L102'>102</a>
-<a name='L103'></a><a href='#L103'>103</a>
-<a name='L104'></a><a href='#L104'>104</a>
-<a name='L105'></a><a href='#L105'>105</a>
-<a name='L106'></a><a href='#L106'>106</a>
-<a name='L107'></a><a href='#L107'>107</a>
-<a name='L108'></a><a href='#L108'>108</a>
-<a name='L109'></a><a href='#L109'>109</a>
-<a name='L110'></a><a href='#L110'>110</a>
-<a name='L111'></a><a href='#L111'>111</a>
-<a name='L112'></a><a href='#L112'>112</a>
-<a name='L113'></a><a href='#L113'>113</a>
-<a name='L114'></a><a href='#L114'>114</a>
-<a name='L115'></a><a href='#L115'>115</a>
-<a name='L116'></a><a href='#L116'>116</a>
-<a name='L117'></a><a href='#L117'>117</a>
-<a name='L118'></a><a href='#L118'>118</a>
-<a name='L119'></a><a href='#L119'>119</a>
-<a name='L120'></a><a href='#L120'>120</a>
-<a name='L121'></a><a href='#L121'>121</a>
-<a name='L122'></a><a href='#L122'>122</a>
-<a name='L123'></a><a href='#L123'>123</a>
-<a name='L124'></a><a href='#L124'>124</a>
-<a name='L125'></a><a href='#L125'>125</a>
-<a name='L126'></a><a href='#L126'>126</a>
-<a name='L127'></a><a href='#L127'>127</a>
-<a name='L128'></a><a href='#L128'>128</a>
-<a name='L129'></a><a href='#L129'>129</a>
-<a name='L130'></a><a href='#L130'>130</a>
-<a name='L131'></a><a href='#L131'>131</a>
-<a name='L132'></a><a href='#L132'>132</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/* Resets and Base Styles */
-* {
- box-sizing: border-box;
-}
-&nbsp;
-body {
- margin: 0;
-}
-&nbsp;
-/* Dashboard Layout */
-.dashboard {
- display: flex;
- flex-direction: column;
- height: 100vh;
- overflow: hidden;
- /* Prevent body scroll */
-}
-&nbsp;
-/* Header styles removed as we moved to sidebar navigation */
-&nbsp;
-.dashboard-content {
- display: flex;
- flex: 1;
- overflow: hidden;
- position: relative;
-}
-&nbsp;
-.dashboard-sidebar {
- width: 11rem;
- background: transparent;
- border-right: 1px solid var(--border-color);
- display: flex;
- flex-direction: column;
- overflow-y: auto;
- transition: margin-left 0.4s ease;
- /* No padding here, handled in FeedList */
-}
-&nbsp;
-.dashboard-sidebar.hidden {
- margin-left: -11rem;
-}
-&nbsp;
-.dashboard-main {
- flex: 1;
- padding: 2rem;
- overflow-y: auto;
- background: var(--bg-color);
- margin-left: 0;
-}
-&nbsp;
-.dashboard-main&gt;* {
- max-width: 35em;
- margin: 0 auto;
-}
-&nbsp;
-.fixed-toggle {
- position: absolute;
- top: 1rem;
- left: 1rem;
- z-index: 1000;
- background: var(--bg-color);
- /* Added bg to be visible over content if needed */
- border: none;
- font-size: 2rem;
- line-height: 1;
- cursor: pointer;
- padding: 0.2rem;
- color: var(--text-color);
- border-radius: 50%;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
- display: flex;
- align-items: center;
- justify-content: center;
-}
-&nbsp;
-.fixed-toggle:hover {
- transform: scale(1.1);
-}
-&nbsp;
-/* Mobile Responsiveness */
-@media (max-width: 768px) {
- .dashboard-sidebar {
- position: fixed;
- top: 0;
- left: 0;
- bottom: 0;
- z-index: 1100;
- box-shadow: 2px 0 10px rgba(0, 0, 0, 0.2);
- width: 14rem;
- /* Slightly wider on mobile for better target area */
- }
-&nbsp;
- .dashboard-sidebar.hidden {
- margin-left: -14rem;
- }
-&nbsp;
- .dashboard-main {
- padding: 1rem;
- padding-top: 4rem;
- /* Space for the toggle button */
- }
-&nbsp;
- .dashboard-main&gt;* {
- max-width: 100%;
- }
-&nbsp;
- /* When sidebar is visible on mobile, we show a backdrop */
- .sidebar-backdrop {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.4);
- z-index: 1050;
- animation: fadeIn 0.3s ease;
- }
-&nbsp;
- .dashboard.sidebar-visible::after {
- display: none;
- }
-}
-&nbsp;
-@keyframes fadeIn {
- from {
- opacity: 0;
- }
-&nbsp;
- to {
- opacity: 1;
- }
-}</pre></td></tr></table></pre>
-
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../sorter.js"></script>
- <script src="../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/App.tsx.html b/frontend/coverage/src/App.tsx.html
deleted file mode 100644
index 6ec66af..0000000
--- a/frontend/coverage/src/App.tsx.html
+++ /dev/null
@@ -1,505 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/App.tsx</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../prettify.css" />
- <link rel="stylesheet" href="../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../index.html">All files</a> / <a href="index.html">src</a> App.tsx</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">65.71% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>23/35</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">60% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>15/25</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">53.84% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>7/13</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">64.7% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>22/34</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line medium'></div>
- <pre><table class="coverage">
-<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
-<a name='L2'></a><a href='#L2'>2</a>
-<a name='L3'></a><a href='#L3'>3</a>
-<a name='L4'></a><a href='#L4'>4</a>
-<a name='L5'></a><a href='#L5'>5</a>
-<a name='L6'></a><a href='#L6'>6</a>
-<a name='L7'></a><a href='#L7'>7</a>
-<a name='L8'></a><a href='#L8'>8</a>
-<a name='L9'></a><a href='#L9'>9</a>
-<a name='L10'></a><a href='#L10'>10</a>
-<a name='L11'></a><a href='#L11'>11</a>
-<a name='L12'></a><a href='#L12'>12</a>
-<a name='L13'></a><a href='#L13'>13</a>
-<a name='L14'></a><a href='#L14'>14</a>
-<a name='L15'></a><a href='#L15'>15</a>
-<a name='L16'></a><a href='#L16'>16</a>
-<a name='L17'></a><a href='#L17'>17</a>
-<a name='L18'></a><a href='#L18'>18</a>
-<a name='L19'></a><a href='#L19'>19</a>
-<a name='L20'></a><a href='#L20'>20</a>
-<a name='L21'></a><a href='#L21'>21</a>
-<a name='L22'></a><a href='#L22'>22</a>
-<a name='L23'></a><a href='#L23'>23</a>
-<a name='L24'></a><a href='#L24'>24</a>
-<a name='L25'></a><a href='#L25'>25</a>
-<a name='L26'></a><a href='#L26'>26</a>
-<a name='L27'></a><a href='#L27'>27</a>
-<a name='L28'></a><a href='#L28'>28</a>
-<a name='L29'></a><a href='#L29'>29</a>
-<a name='L30'></a><a href='#L30'>30</a>
-<a name='L31'></a><a href='#L31'>31</a>
-<a name='L32'></a><a href='#L32'>32</a>
-<a name='L33'></a><a href='#L33'>33</a>
-<a name='L34'></a><a href='#L34'>34</a>
-<a name='L35'></a><a href='#L35'>35</a>
-<a name='L36'></a><a href='#L36'>36</a>
-<a name='L37'></a><a href='#L37'>37</a>
-<a name='L38'></a><a href='#L38'>38</a>
-<a name='L39'></a><a href='#L39'>39</a>
-<a name='L40'></a><a href='#L40'>40</a>
-<a name='L41'></a><a href='#L41'>41</a>
-<a name='L42'></a><a href='#L42'>42</a>
-<a name='L43'></a><a href='#L43'>43</a>
-<a name='L44'></a><a href='#L44'>44</a>
-<a name='L45'></a><a href='#L45'>45</a>
-<a name='L46'></a><a href='#L46'>46</a>
-<a name='L47'></a><a href='#L47'>47</a>
-<a name='L48'></a><a href='#L48'>48</a>
-<a name='L49'></a><a href='#L49'>49</a>
-<a name='L50'></a><a href='#L50'>50</a>
-<a name='L51'></a><a href='#L51'>51</a>
-<a name='L52'></a><a href='#L52'>52</a>
-<a name='L53'></a><a href='#L53'>53</a>
-<a name='L54'></a><a href='#L54'>54</a>
-<a name='L55'></a><a href='#L55'>55</a>
-<a name='L56'></a><a href='#L56'>56</a>
-<a name='L57'></a><a href='#L57'>57</a>
-<a name='L58'></a><a href='#L58'>58</a>
-<a name='L59'></a><a href='#L59'>59</a>
-<a name='L60'></a><a href='#L60'>60</a>
-<a name='L61'></a><a href='#L61'>61</a>
-<a name='L62'></a><a href='#L62'>62</a>
-<a name='L63'></a><a href='#L63'>63</a>
-<a name='L64'></a><a href='#L64'>64</a>
-<a name='L65'></a><a href='#L65'>65</a>
-<a name='L66'></a><a href='#L66'>66</a>
-<a name='L67'></a><a href='#L67'>67</a>
-<a name='L68'></a><a href='#L68'>68</a>
-<a name='L69'></a><a href='#L69'>69</a>
-<a name='L70'></a><a href='#L70'>70</a>
-<a name='L71'></a><a href='#L71'>71</a>
-<a name='L72'></a><a href='#L72'>72</a>
-<a name='L73'></a><a href='#L73'>73</a>
-<a name='L74'></a><a href='#L74'>74</a>
-<a name='L75'></a><a href='#L75'>75</a>
-<a name='L76'></a><a href='#L76'>76</a>
-<a name='L77'></a><a href='#L77'>77</a>
-<a name='L78'></a><a href='#L78'>78</a>
-<a name='L79'></a><a href='#L79'>79</a>
-<a name='L80'></a><a href='#L80'>80</a>
-<a name='L81'></a><a href='#L81'>81</a>
-<a name='L82'></a><a href='#L82'>82</a>
-<a name='L83'></a><a href='#L83'>83</a>
-<a name='L84'></a><a href='#L84'>84</a>
-<a name='L85'></a><a href='#L85'>85</a>
-<a name='L86'></a><a href='#L86'>86</a>
-<a name='L87'></a><a href='#L87'>87</a>
-<a name='L88'></a><a href='#L88'>88</a>
-<a name='L89'></a><a href='#L89'>89</a>
-<a name='L90'></a><a href='#L90'>90</a>
-<a name='L91'></a><a href='#L91'>91</a>
-<a name='L92'></a><a href='#L92'>92</a>
-<a name='L93'></a><a href='#L93'>93</a>
-<a name='L94'></a><a href='#L94'>94</a>
-<a name='L95'></a><a href='#L95'>95</a>
-<a name='L96'></a><a href='#L96'>96</a>
-<a name='L97'></a><a href='#L97'>97</a>
-<a name='L98'></a><a href='#L98'>98</a>
-<a name='L99'></a><a href='#L99'>99</a>
-<a name='L100'></a><a href='#L100'>100</a>
-<a name='L101'></a><a href='#L101'>101</a>
-<a name='L102'></a><a href='#L102'>102</a>
-<a name='L103'></a><a href='#L103'>103</a>
-<a name='L104'></a><a href='#L104'>104</a>
-<a name='L105'></a><a href='#L105'>105</a>
-<a name='L106'></a><a href='#L106'>106</a>
-<a name='L107'></a><a href='#L107'>107</a>
-<a name='L108'></a><a href='#L108'>108</a>
-<a name='L109'></a><a href='#L109'>109</a>
-<a name='L110'></a><a href='#L110'>110</a>
-<a name='L111'></a><a href='#L111'>111</a>
-<a name='L112'></a><a href='#L112'>112</a>
-<a name='L113'></a><a href='#L113'>113</a>
-<a name='L114'></a><a href='#L114'>114</a>
-<a name='L115'></a><a href='#L115'>115</a>
-<a name='L116'></a><a href='#L116'>116</a>
-<a name='L117'></a><a href='#L117'>117</a>
-<a name='L118'></a><a href='#L118'>118</a>
-<a name='L119'></a><a href='#L119'>119</a>
-<a name='L120'></a><a href='#L120'>120</a>
-<a name='L121'></a><a href='#L121'>121</a>
-<a name='L122'></a><a href='#L122'>122</a>
-<a name='L123'></a><a href='#L123'>123</a>
-<a name='L124'></a><a href='#L124'>124</a>
-<a name='L125'></a><a href='#L125'>125</a>
-<a name='L126'></a><a href='#L126'>126</a>
-<a name='L127'></a><a href='#L127'>127</a>
-<a name='L128'></a><a href='#L128'>128</a>
-<a name='L129'></a><a href='#L129'>129</a>
-<a name='L130'></a><a href='#L130'>130</a>
-<a name='L131'></a><a href='#L131'>131</a>
-<a name='L132'></a><a href='#L132'>132</a>
-<a name='L133'></a><a href='#L133'>133</a>
-<a name='L134'></a><a href='#L134'>134</a>
-<a name='L135'></a><a href='#L135'>135</a>
-<a name='L136'></a><a href='#L136'>136</a>
-<a name='L137'></a><a href='#L137'>137</a>
-<a name='L138'></a><a href='#L138'>138</a>
-<a name='L139'></a><a href='#L139'>139</a>
-<a name='L140'></a><a href='#L140'>140</a>
-<a name='L141'></a><a href='#L141'>141</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import React, { useEffect, useState } from 'react';
-import { BrowserRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom';
-import Login from './components/Login';
-import './App.css';
-import { apiFetch } from './utils';
-&nbsp;
-// Protected Route wrapper
-function RequireAuth({ children }: { children: React.ReactElement }) {
- const [auth, setAuth] = useState&lt;boolean | null&gt;(null);
- const location = useLocation();
-&nbsp;
- useEffect(() =&gt; {
- apiFetch('/api/auth')
- .then((res) =&gt; {
- if (res.ok) {
- setAuth(true);
- } else <span class="missing-if-branch" title="else path not taken" >E</span>{
-<span class="cstat-no" title="statement not covered" > setAuth(false);</span>
- }
- })
- .catch(<span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >s</span>etAuth(false))</span>;
- }, []);
-&nbsp;
- if (auth === null) {
- return &lt;div&gt;Loading...&lt;/div&gt;;
- }
-&nbsp;
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!auth) {
-<span class="cstat-no" title="statement not covered" > return &lt;Navigate to="/login" state={{ from: location }} replace /&gt;;</span>
- }
-&nbsp;
- return children;
-}
-&nbsp;
-import FeedList from './components/FeedList';
-import FeedItems from './components/FeedItems';
-import Settings from './components/Settings';
-&nbsp;
-interface DashboardProps {
- theme: string;
- setTheme: (t: string) =&gt; void;
- fontTheme: string;
- setFontTheme: (t: string) =&gt; void;
-}
-&nbsp;
-function Dashboard({ theme, setTheme, fontTheme, setFontTheme }: DashboardProps) {
- const [sidebarVisible, setSidebarVisible] = useState(window.innerWidth &gt; 768);
-&nbsp;
- useEffect(() =&gt; {
- const handleResize = <span class="fstat-no" title="function not covered" >() =&gt; {</span>
-<span class="cstat-no" title="statement not covered" > if (window.innerWidth &gt; 768) {</span>
-<span class="cstat-no" title="statement not covered" > setSidebarVisible(true);</span>
- } else {
-<span class="cstat-no" title="statement not covered" > setSidebarVisible(false);</span>
- }
- };
- window.addEventListener('resize', handleResize);
- return () =&gt; window.removeEventListener('resize', handleResize);
- }, []);
-&nbsp;
- return (
- &lt;div
- className={`dashboard ${sidebarVisible ? 'sidebar-visible' : <span class="branch-1 cbranch-no" title="branch not covered" >'sidebar-hidden'}</span> theme-${theme} font-${fontTheme}`}
- &gt;
- &lt;div className="dashboard-content"&gt;
- {(!sidebarVisible || window.innerWidth &lt;= 768) &amp;&amp; (
-<span class="branch-2 cbranch-no" title="branch not covered" > &lt;button</span>
- className="sidebar-toggle fixed-toggle"
- onClick={<span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >s</span>etSidebarVisible(!sidebarVisible)}</span>
- title={sidebarVisible ? "Hide Sidebar" : "Show Sidebar"}
- &gt;
- 🐱
- &lt;/button&gt;
- )}
- {sidebarVisible &amp;&amp; (
- &lt;div
- className="sidebar-backdrop"
- onClick={<span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >s</span>etSidebarVisible(false)}</span>
- /&gt;
- )}
- &lt;aside className={`dashboard-sidebar ${sidebarVisible ? '' : <span class="branch-1 cbranch-no" title="branch not covered" >'hidden'}</span>`}&gt;
- &lt;FeedList
- theme={theme}
- setTheme={setTheme}
- setSidebarVisible={setSidebarVisible}
- isMobile={window.innerWidth &lt;= 768}
- /&gt;
- &lt;/aside&gt;
- &lt;main className="dashboard-main"&gt;
- &lt;Routes&gt;
- &lt;Route path="/feed/:feedId" element={&lt;FeedItems /&gt;} /&gt;
- &lt;Route path="/tag/:tagName" element={&lt;FeedItems /&gt;} /&gt;
- &lt;Route path="/settings" element={&lt;Settings fontTheme={fontTheme} setFontTheme={setFontTheme} /&gt;} /&gt;
- &lt;Route path="/" element={&lt;FeedItems /&gt;} /&gt;
- &lt;/Routes&gt;
- &lt;/main&gt;
- &lt;/div&gt;
- &lt;/div&gt;
- );
-}
-&nbsp;
-function App() {
- const [theme, setTheme] = useState(localStorage.getItem('neko-theme') || 'light');
- const [fontTheme, setFontTheme] = useState(localStorage.getItem('neko-font-theme') || 'default');
-&nbsp;
- const handleSetTheme = <span class="fstat-no" title="function not covered" >(n</span>ewTheme: string) =&gt; {
-<span class="cstat-no" title="statement not covered" > setTheme(newTheme);</span>
-<span class="cstat-no" title="statement not covered" > localStorage.setItem('neko-theme', newTheme);</span>
- };
-&nbsp;
- const handleSetFontTheme = <span class="fstat-no" title="function not covered" >(n</span>ewFontTheme: string) =&gt; {
-<span class="cstat-no" title="statement not covered" > setFontTheme(newFontTheme);</span>
-<span class="cstat-no" title="statement not covered" > localStorage.setItem('neko-font-theme', newFontTheme);</span>
- };
-&nbsp;
- const basename = window.location.pathname.startsWith('/v2') ? '/v2' : <span class="branch-1 cbranch-no" title="branch not covered" >'/';</span>
-&nbsp;
- return (
- &lt;BrowserRouter basename={basename}&gt;
- &lt;Routes&gt;
- &lt;Route path="/login" element={&lt;Login /&gt;} /&gt;
- &lt;Route
- path="/*"
- element={
- &lt;RequireAuth&gt;
- &lt;Dashboard
- theme={theme}
- setTheme={handleSetTheme}
- fontTheme={fontTheme}
- setFontTheme={handleSetFontTheme}
- /&gt;
- &lt;/RequireAuth&gt;
- }
- /&gt;
- &lt;/Routes&gt;
- &lt;/BrowserRouter&gt;
- );
-}
-&nbsp;
-export default App;
-&nbsp;</pre></td></tr></table></pre>
-
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../sorter.js"></script>
- <script src="../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/components/FeedItem.css.html b/frontend/coverage/src/components/FeedItem.css.html
deleted file mode 100644
index 192959f..0000000
--- a/frontend/coverage/src/components/FeedItem.css.html
+++ /dev/null
@@ -1,526 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/components/FeedItem.css</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../../prettify.css" />
- <link rel="stylesheet" href="../../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedItem.css</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line low'></div>
- <pre><table class="coverage">
-<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
-<a name='L2'></a><a href='#L2'>2</a>
-<a name='L3'></a><a href='#L3'>3</a>
-<a name='L4'></a><a href='#L4'>4</a>
-<a name='L5'></a><a href='#L5'>5</a>
-<a name='L6'></a><a href='#L6'>6</a>
-<a name='L7'></a><a href='#L7'>7</a>
-<a name='L8'></a><a href='#L8'>8</a>
-<a name='L9'></a><a href='#L9'>9</a>
-<a name='L10'></a><a href='#L10'>10</a>
-<a name='L11'></a><a href='#L11'>11</a>
-<a name='L12'></a><a href='#L12'>12</a>
-<a name='L13'></a><a href='#L13'>13</a>
-<a name='L14'></a><a href='#L14'>14</a>
-<a name='L15'></a><a href='#L15'>15</a>
-<a name='L16'></a><a href='#L16'>16</a>
-<a name='L17'></a><a href='#L17'>17</a>
-<a name='L18'></a><a href='#L18'>18</a>
-<a name='L19'></a><a href='#L19'>19</a>
-<a name='L20'></a><a href='#L20'>20</a>
-<a name='L21'></a><a href='#L21'>21</a>
-<a name='L22'></a><a href='#L22'>22</a>
-<a name='L23'></a><a href='#L23'>23</a>
-<a name='L24'></a><a href='#L24'>24</a>
-<a name='L25'></a><a href='#L25'>25</a>
-<a name='L26'></a><a href='#L26'>26</a>
-<a name='L27'></a><a href='#L27'>27</a>
-<a name='L28'></a><a href='#L28'>28</a>
-<a name='L29'></a><a href='#L29'>29</a>
-<a name='L30'></a><a href='#L30'>30</a>
-<a name='L31'></a><a href='#L31'>31</a>
-<a name='L32'></a><a href='#L32'>32</a>
-<a name='L33'></a><a href='#L33'>33</a>
-<a name='L34'></a><a href='#L34'>34</a>
-<a name='L35'></a><a href='#L35'>35</a>
-<a name='L36'></a><a href='#L36'>36</a>
-<a name='L37'></a><a href='#L37'>37</a>
-<a name='L38'></a><a href='#L38'>38</a>
-<a name='L39'></a><a href='#L39'>39</a>
-<a name='L40'></a><a href='#L40'>40</a>
-<a name='L41'></a><a href='#L41'>41</a>
-<a name='L42'></a><a href='#L42'>42</a>
-<a name='L43'></a><a href='#L43'>43</a>
-<a name='L44'></a><a href='#L44'>44</a>
-<a name='L45'></a><a href='#L45'>45</a>
-<a name='L46'></a><a href='#L46'>46</a>
-<a name='L47'></a><a href='#L47'>47</a>
-<a name='L48'></a><a href='#L48'>48</a>
-<a name='L49'></a><a href='#L49'>49</a>
-<a name='L50'></a><a href='#L50'>50</a>
-<a name='L51'></a><a href='#L51'>51</a>
-<a name='L52'></a><a href='#L52'>52</a>
-<a name='L53'></a><a href='#L53'>53</a>
-<a name='L54'></a><a href='#L54'>54</a>
-<a name='L55'></a><a href='#L55'>55</a>
-<a name='L56'></a><a href='#L56'>56</a>
-<a name='L57'></a><a href='#L57'>57</a>
-<a name='L58'></a><a href='#L58'>58</a>
-<a name='L59'></a><a href='#L59'>59</a>
-<a name='L60'></a><a href='#L60'>60</a>
-<a name='L61'></a><a href='#L61'>61</a>
-<a name='L62'></a><a href='#L62'>62</a>
-<a name='L63'></a><a href='#L63'>63</a>
-<a name='L64'></a><a href='#L64'>64</a>
-<a name='L65'></a><a href='#L65'>65</a>
-<a name='L66'></a><a href='#L66'>66</a>
-<a name='L67'></a><a href='#L67'>67</a>
-<a name='L68'></a><a href='#L68'>68</a>
-<a name='L69'></a><a href='#L69'>69</a>
-<a name='L70'></a><a href='#L70'>70</a>
-<a name='L71'></a><a href='#L71'>71</a>
-<a name='L72'></a><a href='#L72'>72</a>
-<a name='L73'></a><a href='#L73'>73</a>
-<a name='L74'></a><a href='#L74'>74</a>
-<a name='L75'></a><a href='#L75'>75</a>
-<a name='L76'></a><a href='#L76'>76</a>
-<a name='L77'></a><a href='#L77'>77</a>
-<a name='L78'></a><a href='#L78'>78</a>
-<a name='L79'></a><a href='#L79'>79</a>
-<a name='L80'></a><a href='#L80'>80</a>
-<a name='L81'></a><a href='#L81'>81</a>
-<a name='L82'></a><a href='#L82'>82</a>
-<a name='L83'></a><a href='#L83'>83</a>
-<a name='L84'></a><a href='#L84'>84</a>
-<a name='L85'></a><a href='#L85'>85</a>
-<a name='L86'></a><a href='#L86'>86</a>
-<a name='L87'></a><a href='#L87'>87</a>
-<a name='L88'></a><a href='#L88'>88</a>
-<a name='L89'></a><a href='#L89'>89</a>
-<a name='L90'></a><a href='#L90'>90</a>
-<a name='L91'></a><a href='#L91'>91</a>
-<a name='L92'></a><a href='#L92'>92</a>
-<a name='L93'></a><a href='#L93'>93</a>
-<a name='L94'></a><a href='#L94'>94</a>
-<a name='L95'></a><a href='#L95'>95</a>
-<a name='L96'></a><a href='#L96'>96</a>
-<a name='L97'></a><a href='#L97'>97</a>
-<a name='L98'></a><a href='#L98'>98</a>
-<a name='L99'></a><a href='#L99'>99</a>
-<a name='L100'></a><a href='#L100'>100</a>
-<a name='L101'></a><a href='#L101'>101</a>
-<a name='L102'></a><a href='#L102'>102</a>
-<a name='L103'></a><a href='#L103'>103</a>
-<a name='L104'></a><a href='#L104'>104</a>
-<a name='L105'></a><a href='#L105'>105</a>
-<a name='L106'></a><a href='#L106'>106</a>
-<a name='L107'></a><a href='#L107'>107</a>
-<a name='L108'></a><a href='#L108'>108</a>
-<a name='L109'></a><a href='#L109'>109</a>
-<a name='L110'></a><a href='#L110'>110</a>
-<a name='L111'></a><a href='#L111'>111</a>
-<a name='L112'></a><a href='#L112'>112</a>
-<a name='L113'></a><a href='#L113'>113</a>
-<a name='L114'></a><a href='#L114'>114</a>
-<a name='L115'></a><a href='#L115'>115</a>
-<a name='L116'></a><a href='#L116'>116</a>
-<a name='L117'></a><a href='#L117'>117</a>
-<a name='L118'></a><a href='#L118'>118</a>
-<a name='L119'></a><a href='#L119'>119</a>
-<a name='L120'></a><a href='#L120'>120</a>
-<a name='L121'></a><a href='#L121'>121</a>
-<a name='L122'></a><a href='#L122'>122</a>
-<a name='L123'></a><a href='#L123'>123</a>
-<a name='L124'></a><a href='#L124'>124</a>
-<a name='L125'></a><a href='#L125'>125</a>
-<a name='L126'></a><a href='#L126'>126</a>
-<a name='L127'></a><a href='#L127'>127</a>
-<a name='L128'></a><a href='#L128'>128</a>
-<a name='L129'></a><a href='#L129'>129</a>
-<a name='L130'></a><a href='#L130'>130</a>
-<a name='L131'></a><a href='#L131'>131</a>
-<a name='L132'></a><a href='#L132'>132</a>
-<a name='L133'></a><a href='#L133'>133</a>
-<a name='L134'></a><a href='#L134'>134</a>
-<a name='L135'></a><a href='#L135'>135</a>
-<a name='L136'></a><a href='#L136'>136</a>
-<a name='L137'></a><a href='#L137'>137</a>
-<a name='L138'></a><a href='#L138'>138</a>
-<a name='L139'></a><a href='#L139'>139</a>
-<a name='L140'></a><a href='#L140'>140</a>
-<a name='L141'></a><a href='#L141'>141</a>
-<a name='L142'></a><a href='#L142'>142</a>
-<a name='L143'></a><a href='#L143'>143</a>
-<a name='L144'></a><a href='#L144'>144</a>
-<a name='L145'></a><a href='#L145'>145</a>
-<a name='L146'></a><a href='#L146'>146</a>
-<a name='L147'></a><a href='#L147'>147</a>
-<a name='L148'></a><a href='#L148'>148</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">.feed-item {
- padding: 1rem;
- margin-top: 5rem;
- list-style: none;
- border-bottom: none;
-}
-&nbsp;
-/* removed read/unread specific font-weight to keep it always bold as requested */
-&nbsp;
-.item-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 0.5rem;
-}
-&nbsp;
-.item-title {
- font-family: var(--font-heading);
- font-size: 1.8rem;
- font-weight: bold;
- text-decoration: none;
- color: var(--link-color);
- display: block;
- flex: 1;
-}
-&nbsp;
-.item-title:hover {
- text-decoration: none;
- color: var(--link-color);
-}
-&nbsp;
-.item-actions {
- display: flex;
- gap: 0.5rem;
- margin-left: 1rem;
-}
-&nbsp;
-/* Legacy controls were simple text/links, but buttons are fine if minimal */
-.star-btn {
- background: none;
- border: none;
- cursor: pointer;
- font-size: 1.25rem;
- padding: 0 0 0 0.5rem;
- vertical-align: middle;
- transition: color 0.2s;
- line-height: 1;
-}
-&nbsp;
-.star-btn.is-starred {
- color: blue;
-}
-&nbsp;
-.star-btn.is-unstarred {
- color: var(--text-color);
- opacity: 0.3;
-}
-&nbsp;
-.star-btn:hover {
- color: blue;
-}
-&nbsp;
-.action-btn {
- background: var(--sidebar-bg);
- border: 1px solid var(--border-color, #ccc);
- cursor: pointer;
- padding: 2px 6px;
- font-size: 1rem;
- color: blue;
- font-weight: bold;
-}
-&nbsp;
-.action-btn:hover {
- background-color: #eee;
-}
-&nbsp;
-.dateline {
- margin-top: 0;
- font-weight: normal;
- font-size: 0.75em;
- color: #ccc;
- margin-bottom: 1rem;
-}
-&nbsp;
-.dateline a {
- color: #ccc;
- text-decoration: none;
-}
-&nbsp;
-.item-description {
- color: var(--text-color);
- line-height: 1.5;
- font-size: 1rem;
- margin-top: 1rem;
-}
-&nbsp;
-.item-description img {
- max-width: 100%;
- height: auto;
- display: block;
- margin: 1rem 0;
-}
-&nbsp;
-.item-description blockquote {
- padding: 1rem 1rem 0 1rem;
- border-left: 4px solid var(--sidebar-bg);
- color: var(--text-color);
- opacity: 0.8;
- margin-left: 0;
-}
-&nbsp;
-.scrape-btn {
- background: var(--bg-color);
- border: 1px solid var(--border-color, #ccc);
- color: blue;
- cursor: pointer;
- font-family: var(--font-heading);
- font-weight: bold;
- font-size: 0.8rem;
- padding: 2px 6px;
- margin-left: 0.5rem;
-}
-&nbsp;
-.scrape-btn:hover {
- background: var(--sidebar-bg);
-}
-&nbsp;
-@media (max-width: 768px) {
- .feed-item {
- margin-top: 2rem;
- padding: 0.5rem;
- }
-&nbsp;
- .item-title {
- font-size: 1.4rem;
- word-break: break-word;
- }
-&nbsp;
- .item-header {
- flex-direction: column;
- gap: 0.5rem;
- }
-&nbsp;
- .item-actions {
- margin-left: 0;
- margin-bottom: 0.5rem;
- }
-}</pre></td></tr></table></pre>
-
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../../sorter.js"></script>
- <script src="../../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/components/FeedItem.tsx.html b/frontend/coverage/src/components/FeedItem.tsx.html
deleted file mode 100644
index 6df1fec..0000000
--- a/frontend/coverage/src/components/FeedItem.tsx.html
+++ /dev/null
@@ -1,430 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/components/FeedItem.tsx</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../../prettify.css" />
- <link rel="stylesheet" href="../../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedItem.tsx</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">78.12% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>25/32</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">86.95% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>20/23</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">83.33% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>10/12</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">80.64% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>25/31</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line medium'></div>
- <pre><table class="coverage">
-<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
-<a name='L2'></a><a href='#L2'>2</a>
-<a name='L3'></a><a href='#L3'>3</a>
-<a name='L4'></a><a href='#L4'>4</a>
-<a name='L5'></a><a href='#L5'>5</a>
-<a name='L6'></a><a href='#L6'>6</a>
-<a name='L7'></a><a href='#L7'>7</a>
-<a name='L8'></a><a href='#L8'>8</a>
-<a name='L9'></a><a href='#L9'>9</a>
-<a name='L10'></a><a href='#L10'>10</a>
-<a name='L11'></a><a href='#L11'>11</a>
-<a name='L12'></a><a href='#L12'>12</a>
-<a name='L13'></a><a href='#L13'>13</a>
-<a name='L14'></a><a href='#L14'>14</a>
-<a name='L15'></a><a href='#L15'>15</a>
-<a name='L16'></a><a href='#L16'>16</a>
-<a name='L17'></a><a href='#L17'>17</a>
-<a name='L18'></a><a href='#L18'>18</a>
-<a name='L19'></a><a href='#L19'>19</a>
-<a name='L20'></a><a href='#L20'>20</a>
-<a name='L21'></a><a href='#L21'>21</a>
-<a name='L22'></a><a href='#L22'>22</a>
-<a name='L23'></a><a href='#L23'>23</a>
-<a name='L24'></a><a href='#L24'>24</a>
-<a name='L25'></a><a href='#L25'>25</a>
-<a name='L26'></a><a href='#L26'>26</a>
-<a name='L27'></a><a href='#L27'>27</a>
-<a name='L28'></a><a href='#L28'>28</a>
-<a name='L29'></a><a href='#L29'>29</a>
-<a name='L30'></a><a href='#L30'>30</a>
-<a name='L31'></a><a href='#L31'>31</a>
-<a name='L32'></a><a href='#L32'>32</a>
-<a name='L33'></a><a href='#L33'>33</a>
-<a name='L34'></a><a href='#L34'>34</a>
-<a name='L35'></a><a href='#L35'>35</a>
-<a name='L36'></a><a href='#L36'>36</a>
-<a name='L37'></a><a href='#L37'>37</a>
-<a name='L38'></a><a href='#L38'>38</a>
-<a name='L39'></a><a href='#L39'>39</a>
-<a name='L40'></a><a href='#L40'>40</a>
-<a name='L41'></a><a href='#L41'>41</a>
-<a name='L42'></a><a href='#L42'>42</a>
-<a name='L43'></a><a href='#L43'>43</a>
-<a name='L44'></a><a href='#L44'>44</a>
-<a name='L45'></a><a href='#L45'>45</a>
-<a name='L46'></a><a href='#L46'>46</a>
-<a name='L47'></a><a href='#L47'>47</a>
-<a name='L48'></a><a href='#L48'>48</a>
-<a name='L49'></a><a href='#L49'>49</a>
-<a name='L50'></a><a href='#L50'>50</a>
-<a name='L51'></a><a href='#L51'>51</a>
-<a name='L52'></a><a href='#L52'>52</a>
-<a name='L53'></a><a href='#L53'>53</a>
-<a name='L54'></a><a href='#L54'>54</a>
-<a name='L55'></a><a href='#L55'>55</a>
-<a name='L56'></a><a href='#L56'>56</a>
-<a name='L57'></a><a href='#L57'>57</a>
-<a name='L58'></a><a href='#L58'>58</a>
-<a name='L59'></a><a href='#L59'>59</a>
-<a name='L60'></a><a href='#L60'>60</a>
-<a name='L61'></a><a href='#L61'>61</a>
-<a name='L62'></a><a href='#L62'>62</a>
-<a name='L63'></a><a href='#L63'>63</a>
-<a name='L64'></a><a href='#L64'>64</a>
-<a name='L65'></a><a href='#L65'>65</a>
-<a name='L66'></a><a href='#L66'>66</a>
-<a name='L67'></a><a href='#L67'>67</a>
-<a name='L68'></a><a href='#L68'>68</a>
-<a name='L69'></a><a href='#L69'>69</a>
-<a name='L70'></a><a href='#L70'>70</a>
-<a name='L71'></a><a href='#L71'>71</a>
-<a name='L72'></a><a href='#L72'>72</a>
-<a name='L73'></a><a href='#L73'>73</a>
-<a name='L74'></a><a href='#L74'>74</a>
-<a name='L75'></a><a href='#L75'>75</a>
-<a name='L76'></a><a href='#L76'>76</a>
-<a name='L77'></a><a href='#L77'>77</a>
-<a name='L78'></a><a href='#L78'>78</a>
-<a name='L79'></a><a href='#L79'>79</a>
-<a name='L80'></a><a href='#L80'>80</a>
-<a name='L81'></a><a href='#L81'>81</a>
-<a name='L82'></a><a href='#L82'>82</a>
-<a name='L83'></a><a href='#L83'>83</a>
-<a name='L84'></a><a href='#L84'>84</a>
-<a name='L85'></a><a href='#L85'>85</a>
-<a name='L86'></a><a href='#L86'>86</a>
-<a name='L87'></a><a href='#L87'>87</a>
-<a name='L88'></a><a href='#L88'>88</a>
-<a name='L89'></a><a href='#L89'>89</a>
-<a name='L90'></a><a href='#L90'>90</a>
-<a name='L91'></a><a href='#L91'>91</a>
-<a name='L92'></a><a href='#L92'>92</a>
-<a name='L93'></a><a href='#L93'>93</a>
-<a name='L94'></a><a href='#L94'>94</a>
-<a name='L95'></a><a href='#L95'>95</a>
-<a name='L96'></a><a href='#L96'>96</a>
-<a name='L97'></a><a href='#L97'>97</a>
-<a name='L98'></a><a href='#L98'>98</a>
-<a name='L99'></a><a href='#L99'>99</a>
-<a name='L100'></a><a href='#L100'>100</a>
-<a name='L101'></a><a href='#L101'>101</a>
-<a name='L102'></a><a href='#L102'>102</a>
-<a name='L103'></a><a href='#L103'>103</a>
-<a name='L104'></a><a href='#L104'>104</a>
-<a name='L105'></a><a href='#L105'>105</a>
-<a name='L106'></a><a href='#L106'>106</a>
-<a name='L107'></a><a href='#L107'>107</a>
-<a name='L108'></a><a href='#L108'>108</a>
-<a name='L109'></a><a href='#L109'>109</a>
-<a name='L110'></a><a href='#L110'>110</a>
-<a name='L111'></a><a href='#L111'>111</a>
-<a name='L112'></a><a href='#L112'>112</a>
-<a name='L113'></a><a href='#L113'>113</a>
-<a name='L114'></a><a href='#L114'>114</a>
-<a name='L115'></a><a href='#L115'>115</a>
-<a name='L116'></a><a href='#L116'>116</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">56x</span>
-<span class="cline-any cline-yes">56x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">56x</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">56x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">56x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">56x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">56x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { useState, useEffect } from 'react';
-import type { Item } from '../types';
-import './FeedItem.css';
-&nbsp;
-import { apiFetch } from '../utils';
-&nbsp;
-interface FeedItemProps {
- item: Item;
-}
-&nbsp;
-export default function FeedItem({ item: initialItem }: FeedItemProps) {
- const [item, setItem] = useState(initialItem);
- const [loading, setLoading] = useState(false);
-&nbsp;
- useEffect(() =&gt; {
- setItem(initialItem);
- }, [initialItem]);
-&nbsp;
- const toggleStar = () =&gt; {
- updateItem({ ...item, starred: !item.starred });
- };
-&nbsp;
- const updateItem = (newItem: Item) =&gt; {
- setLoading(true);
- // Optimistic update
- const previousItem = item;
- setItem(newItem);
-&nbsp;
- apiFetch(`/api/item/${newItem._id}`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- _id: newItem._id,
- read: newItem.read,
- starred: newItem.starred,
- }),
- })
- .then((res) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) {
-<span class="cstat-no" title="statement not covered" > throw new Error('Failed to update item');</span>
- }
- return res.json();
- })
- .then(() =&gt; {
- // Confirm with server response if needed, but for now we trust the optimistic update
- // or we could setItem(updated) if the server returns the full object
- setLoading(false);
- })
- .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; {
-<span class="cstat-no" title="statement not covered" > console.error('Error updating item:', err);</span>
- // Revert on error
-<span class="cstat-no" title="statement not covered" > setItem(previousItem);</span>
-<span class="cstat-no" title="statement not covered" > setLoading(false);</span>
- });
- };
-&nbsp;
- const loadFullContent = (e: React.MouseEvent) =&gt; {
- e.stopPropagation();
- setLoading(true);
- apiFetch(`/api/item/${item._id}`)
- .then((res) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to fetch full content');</span>
- return res.json();
- })
- .then((data) =&gt; {
- setItem({ ...item, ...data });
- setLoading(false);
- })
- .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; {
-<span class="cstat-no" title="statement not covered" > console.error('Error fetching full content:', err);</span>
-<span class="cstat-no" title="statement not covered" > setLoading(false);</span>
- });
- };
-&nbsp;
- return (
- &lt;li className={`feed-item ${item.read ? 'read' : 'unread'} ${loading ? 'loading' : ''}`}&gt;
- &lt;div className="item-header"&gt;
- &lt;a href={item.url} target="_blank" rel="noopener noreferrer" className="item-title"&gt;
- {item.title || <span class="branch-1 cbranch-no" title="branch not covered" >'(No Title)'}</span>
- &lt;/a&gt;
- &lt;button
- onClick={(e) =&gt; {
- e.stopPropagation();
- toggleStar();
- }}
- className={`star-btn ${item.starred ? 'is-starred' : 'is-unstarred'}`}
- title={item.starred ? 'Unstar' : 'Star'}
- &gt;
- ★
- &lt;/button&gt;
- &lt;/div&gt;
- &lt;div className="dateline"&gt;
- &lt;a href={item.url} target="_blank" rel="noopener noreferrer"&gt;
- {new Date(item.publish_date).toLocaleDateString()}
- {item.feed_title &amp;&amp; ` - ${item.feed_title}`}
- &lt;/a&gt;
- &lt;div className="item-actions" style={{ display: 'inline-block', float: 'right' }}&gt;
- {!item.full_content &amp;&amp; (
- &lt;button onClick={loadFullContent} className="scrape-btn" title="Load Full Content"&gt;
- text
- &lt;/button&gt;
- )}
- &lt;/div&gt;
- &lt;/div&gt;
- {(item.full_content || item.description) &amp;&amp; (
- &lt;div
- className="item-description"
- dangerouslySetInnerHTML={{ __html: item.full_content || item.description }}
- /&gt;
- )}
- &lt;/li&gt;
- );
-}
-&nbsp;</pre></td></tr></table></pre>
-
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../../sorter.js"></script>
- <script src="../../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/components/FeedItems.css.html b/frontend/coverage/src/components/FeedItems.css.html
deleted file mode 100644
index 66a3307..0000000
--- a/frontend/coverage/src/components/FeedItems.css.html
+++ /dev/null
@@ -1,151 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/components/FeedItems.css</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../../prettify.css" />
- <link rel="stylesheet" href="../../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedItems.css</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line low'></div>
- <pre><table class="coverage">
-<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
-<a name='L2'></a><a href='#L2'>2</a>
-<a name='L3'></a><a href='#L3'>3</a>
-<a name='L4'></a><a href='#L4'>4</a>
-<a name='L5'></a><a href='#L5'>5</a>
-<a name='L6'></a><a href='#L6'>6</a>
-<a name='L7'></a><a href='#L7'>7</a>
-<a name='L8'></a><a href='#L8'>8</a>
-<a name='L9'></a><a href='#L9'>9</a>
-<a name='L10'></a><a href='#L10'>10</a>
-<a name='L11'></a><a href='#L11'>11</a>
-<a name='L12'></a><a href='#L12'>12</a>
-<a name='L13'></a><a href='#L13'>13</a>
-<a name='L14'></a><a href='#L14'>14</a>
-<a name='L15'></a><a href='#L15'>15</a>
-<a name='L16'></a><a href='#L16'>16</a>
-<a name='L17'></a><a href='#L17'>17</a>
-<a name='L18'></a><a href='#L18'>18</a>
-<a name='L19'></a><a href='#L19'>19</a>
-<a name='L20'></a><a href='#L20'>20</a>
-<a name='L21'></a><a href='#L21'>21</a>
-<a name='L22'></a><a href='#L22'>22</a>
-<a name='L23'></a><a href='#L23'>23</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">.feed-items {
- padding: 1rem 0;
- /* Removing horizontal padding to avoid double-padding with FeedItem */
-}
-&nbsp;
-.feed-items h2 {
- margin-top: 0;
- border-bottom: 2px solid var(--border-color);
- padding-bottom: 0.5rem;
-}
-&nbsp;
-.item-list {
- list-style: none;
- padding: 0;
-}
-&nbsp;
-.loading-more {
- padding: 2rem;
- text-align: center;
- color: #888;
- font-size: 0.9rem;
- min-height: 50px;
-}</pre></td></tr></table></pre>
-
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../../sorter.js"></script>
- <script src="../../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/components/FeedItems.tsx.html b/frontend/coverage/src/components/FeedItems.tsx.html
deleted file mode 100644
index 9811743..0000000
--- a/frontend/coverage/src/components/FeedItems.tsx.html
+++ /dev/null
@@ -1,838 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/components/FeedItems.tsx</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../../prettify.css" />
- <link rel="stylesheet" href="../../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedItems.tsx</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">89.23% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>116/130</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">76.19% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>64/84</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">87.09% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>27/31</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">89.07% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>106/119</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line high'></div>
- <pre><table class="coverage">
-<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
-<a name='L2'></a><a href='#L2'>2</a>
-<a name='L3'></a><a href='#L3'>3</a>
-<a name='L4'></a><a href='#L4'>4</a>
-<a name='L5'></a><a href='#L5'>5</a>
-<a name='L6'></a><a href='#L6'>6</a>
-<a name='L7'></a><a href='#L7'>7</a>
-<a name='L8'></a><a href='#L8'>8</a>
-<a name='L9'></a><a href='#L9'>9</a>
-<a name='L10'></a><a href='#L10'>10</a>
-<a name='L11'></a><a href='#L11'>11</a>
-<a name='L12'></a><a href='#L12'>12</a>
-<a name='L13'></a><a href='#L13'>13</a>
-<a name='L14'></a><a href='#L14'>14</a>
-<a name='L15'></a><a href='#L15'>15</a>
-<a name='L16'></a><a href='#L16'>16</a>
-<a name='L17'></a><a href='#L17'>17</a>
-<a name='L18'></a><a href='#L18'>18</a>
-<a name='L19'></a><a href='#L19'>19</a>
-<a name='L20'></a><a href='#L20'>20</a>
-<a name='L21'></a><a href='#L21'>21</a>
-<a name='L22'></a><a href='#L22'>22</a>
-<a name='L23'></a><a href='#L23'>23</a>
-<a name='L24'></a><a href='#L24'>24</a>
-<a name='L25'></a><a href='#L25'>25</a>
-<a name='L26'></a><a href='#L26'>26</a>
-<a name='L27'></a><a href='#L27'>27</a>
-<a name='L28'></a><a href='#L28'>28</a>
-<a name='L29'></a><a href='#L29'>29</a>
-<a name='L30'></a><a href='#L30'>30</a>
-<a name='L31'></a><a href='#L31'>31</a>
-<a name='L32'></a><a href='#L32'>32</a>
-<a name='L33'></a><a href='#L33'>33</a>
-<a name='L34'></a><a href='#L34'>34</a>
-<a name='L35'></a><a href='#L35'>35</a>
-<a name='L36'></a><a href='#L36'>36</a>
-<a name='L37'></a><a href='#L37'>37</a>
-<a name='L38'></a><a href='#L38'>38</a>
-<a name='L39'></a><a href='#L39'>39</a>
-<a name='L40'></a><a href='#L40'>40</a>
-<a name='L41'></a><a href='#L41'>41</a>
-<a name='L42'></a><a href='#L42'>42</a>
-<a name='L43'></a><a href='#L43'>43</a>
-<a name='L44'></a><a href='#L44'>44</a>
-<a name='L45'></a><a href='#L45'>45</a>
-<a name='L46'></a><a href='#L46'>46</a>
-<a name='L47'></a><a href='#L47'>47</a>
-<a name='L48'></a><a href='#L48'>48</a>
-<a name='L49'></a><a href='#L49'>49</a>
-<a name='L50'></a><a href='#L50'>50</a>
-<a name='L51'></a><a href='#L51'>51</a>
-<a name='L52'></a><a href='#L52'>52</a>
-<a name='L53'></a><a href='#L53'>53</a>
-<a name='L54'></a><a href='#L54'>54</a>
-<a name='L55'></a><a href='#L55'>55</a>
-<a name='L56'></a><a href='#L56'>56</a>
-<a name='L57'></a><a href='#L57'>57</a>
-<a name='L58'></a><a href='#L58'>58</a>
-<a name='L59'></a><a href='#L59'>59</a>
-<a name='L60'></a><a href='#L60'>60</a>
-<a name='L61'></a><a href='#L61'>61</a>
-<a name='L62'></a><a href='#L62'>62</a>
-<a name='L63'></a><a href='#L63'>63</a>
-<a name='L64'></a><a href='#L64'>64</a>
-<a name='L65'></a><a href='#L65'>65</a>
-<a name='L66'></a><a href='#L66'>66</a>
-<a name='L67'></a><a href='#L67'>67</a>
-<a name='L68'></a><a href='#L68'>68</a>
-<a name='L69'></a><a href='#L69'>69</a>
-<a name='L70'></a><a href='#L70'>70</a>
-<a name='L71'></a><a href='#L71'>71</a>
-<a name='L72'></a><a href='#L72'>72</a>
-<a name='L73'></a><a href='#L73'>73</a>
-<a name='L74'></a><a href='#L74'>74</a>
-<a name='L75'></a><a href='#L75'>75</a>
-<a name='L76'></a><a href='#L76'>76</a>
-<a name='L77'></a><a href='#L77'>77</a>
-<a name='L78'></a><a href='#L78'>78</a>
-<a name='L79'></a><a href='#L79'>79</a>
-<a name='L80'></a><a href='#L80'>80</a>
-<a name='L81'></a><a href='#L81'>81</a>
-<a name='L82'></a><a href='#L82'>82</a>
-<a name='L83'></a><a href='#L83'>83</a>
-<a name='L84'></a><a href='#L84'>84</a>
-<a name='L85'></a><a href='#L85'>85</a>
-<a name='L86'></a><a href='#L86'>86</a>
-<a name='L87'></a><a href='#L87'>87</a>
-<a name='L88'></a><a href='#L88'>88</a>
-<a name='L89'></a><a href='#L89'>89</a>
-<a name='L90'></a><a href='#L90'>90</a>
-<a name='L91'></a><a href='#L91'>91</a>
-<a name='L92'></a><a href='#L92'>92</a>
-<a name='L93'></a><a href='#L93'>93</a>
-<a name='L94'></a><a href='#L94'>94</a>
-<a name='L95'></a><a href='#L95'>95</a>
-<a name='L96'></a><a href='#L96'>96</a>
-<a name='L97'></a><a href='#L97'>97</a>
-<a name='L98'></a><a href='#L98'>98</a>
-<a name='L99'></a><a href='#L99'>99</a>
-<a name='L100'></a><a href='#L100'>100</a>
-<a name='L101'></a><a href='#L101'>101</a>
-<a name='L102'></a><a href='#L102'>102</a>
-<a name='L103'></a><a href='#L103'>103</a>
-<a name='L104'></a><a href='#L104'>104</a>
-<a name='L105'></a><a href='#L105'>105</a>
-<a name='L106'></a><a href='#L106'>106</a>
-<a name='L107'></a><a href='#L107'>107</a>
-<a name='L108'></a><a href='#L108'>108</a>
-<a name='L109'></a><a href='#L109'>109</a>
-<a name='L110'></a><a href='#L110'>110</a>
-<a name='L111'></a><a href='#L111'>111</a>
-<a name='L112'></a><a href='#L112'>112</a>
-<a name='L113'></a><a href='#L113'>113</a>
-<a name='L114'></a><a href='#L114'>114</a>
-<a name='L115'></a><a href='#L115'>115</a>
-<a name='L116'></a><a href='#L116'>116</a>
-<a name='L117'></a><a href='#L117'>117</a>
-<a name='L118'></a><a href='#L118'>118</a>
-<a name='L119'></a><a href='#L119'>119</a>
-<a name='L120'></a><a href='#L120'>120</a>
-<a name='L121'></a><a href='#L121'>121</a>
-<a name='L122'></a><a href='#L122'>122</a>
-<a name='L123'></a><a href='#L123'>123</a>
-<a name='L124'></a><a href='#L124'>124</a>
-<a name='L125'></a><a href='#L125'>125</a>
-<a name='L126'></a><a href='#L126'>126</a>
-<a name='L127'></a><a href='#L127'>127</a>
-<a name='L128'></a><a href='#L128'>128</a>
-<a name='L129'></a><a href='#L129'>129</a>
-<a name='L130'></a><a href='#L130'>130</a>
-<a name='L131'></a><a href='#L131'>131</a>
-<a name='L132'></a><a href='#L132'>132</a>
-<a name='L133'></a><a href='#L133'>133</a>
-<a name='L134'></a><a href='#L134'>134</a>
-<a name='L135'></a><a href='#L135'>135</a>
-<a name='L136'></a><a href='#L136'>136</a>
-<a name='L137'></a><a href='#L137'>137</a>
-<a name='L138'></a><a href='#L138'>138</a>
-<a name='L139'></a><a href='#L139'>139</a>
-<a name='L140'></a><a href='#L140'>140</a>
-<a name='L141'></a><a href='#L141'>141</a>
-<a name='L142'></a><a href='#L142'>142</a>
-<a name='L143'></a><a href='#L143'>143</a>
-<a name='L144'></a><a href='#L144'>144</a>
-<a name='L145'></a><a href='#L145'>145</a>
-<a name='L146'></a><a href='#L146'>146</a>
-<a name='L147'></a><a href='#L147'>147</a>
-<a name='L148'></a><a href='#L148'>148</a>
-<a name='L149'></a><a href='#L149'>149</a>
-<a name='L150'></a><a href='#L150'>150</a>
-<a name='L151'></a><a href='#L151'>151</a>
-<a name='L152'></a><a href='#L152'>152</a>
-<a name='L153'></a><a href='#L153'>153</a>
-<a name='L154'></a><a href='#L154'>154</a>
-<a name='L155'></a><a href='#L155'>155</a>
-<a name='L156'></a><a href='#L156'>156</a>
-<a name='L157'></a><a href='#L157'>157</a>
-<a name='L158'></a><a href='#L158'>158</a>
-<a name='L159'></a><a href='#L159'>159</a>
-<a name='L160'></a><a href='#L160'>160</a>
-<a name='L161'></a><a href='#L161'>161</a>
-<a name='L162'></a><a href='#L162'>162</a>
-<a name='L163'></a><a href='#L163'>163</a>
-<a name='L164'></a><a href='#L164'>164</a>
-<a name='L165'></a><a href='#L165'>165</a>
-<a name='L166'></a><a href='#L166'>166</a>
-<a name='L167'></a><a href='#L167'>167</a>
-<a name='L168'></a><a href='#L168'>168</a>
-<a name='L169'></a><a href='#L169'>169</a>
-<a name='L170'></a><a href='#L170'>170</a>
-<a name='L171'></a><a href='#L171'>171</a>
-<a name='L172'></a><a href='#L172'>172</a>
-<a name='L173'></a><a href='#L173'>173</a>
-<a name='L174'></a><a href='#L174'>174</a>
-<a name='L175'></a><a href='#L175'>175</a>
-<a name='L176'></a><a href='#L176'>176</a>
-<a name='L177'></a><a href='#L177'>177</a>
-<a name='L178'></a><a href='#L178'>178</a>
-<a name='L179'></a><a href='#L179'>179</a>
-<a name='L180'></a><a href='#L180'>180</a>
-<a name='L181'></a><a href='#L181'>181</a>
-<a name='L182'></a><a href='#L182'>182</a>
-<a name='L183'></a><a href='#L183'>183</a>
-<a name='L184'></a><a href='#L184'>184</a>
-<a name='L185'></a><a href='#L185'>185</a>
-<a name='L186'></a><a href='#L186'>186</a>
-<a name='L187'></a><a href='#L187'>187</a>
-<a name='L188'></a><a href='#L188'>188</a>
-<a name='L189'></a><a href='#L189'>189</a>
-<a name='L190'></a><a href='#L190'>190</a>
-<a name='L191'></a><a href='#L191'>191</a>
-<a name='L192'></a><a href='#L192'>192</a>
-<a name='L193'></a><a href='#L193'>193</a>
-<a name='L194'></a><a href='#L194'>194</a>
-<a name='L195'></a><a href='#L195'>195</a>
-<a name='L196'></a><a href='#L196'>196</a>
-<a name='L197'></a><a href='#L197'>197</a>
-<a name='L198'></a><a href='#L198'>198</a>
-<a name='L199'></a><a href='#L199'>199</a>
-<a name='L200'></a><a href='#L200'>200</a>
-<a name='L201'></a><a href='#L201'>201</a>
-<a name='L202'></a><a href='#L202'>202</a>
-<a name='L203'></a><a href='#L203'>203</a>
-<a name='L204'></a><a href='#L204'>204</a>
-<a name='L205'></a><a href='#L205'>205</a>
-<a name='L206'></a><a href='#L206'>206</a>
-<a name='L207'></a><a href='#L207'>207</a>
-<a name='L208'></a><a href='#L208'>208</a>
-<a name='L209'></a><a href='#L209'>209</a>
-<a name='L210'></a><a href='#L210'>210</a>
-<a name='L211'></a><a href='#L211'>211</a>
-<a name='L212'></a><a href='#L212'>212</a>
-<a name='L213'></a><a href='#L213'>213</a>
-<a name='L214'></a><a href='#L214'>214</a>
-<a name='L215'></a><a href='#L215'>215</a>
-<a name='L216'></a><a href='#L216'>216</a>
-<a name='L217'></a><a href='#L217'>217</a>
-<a name='L218'></a><a href='#L218'>218</a>
-<a name='L219'></a><a href='#L219'>219</a>
-<a name='L220'></a><a href='#L220'>220</a>
-<a name='L221'></a><a href='#L221'>221</a>
-<a name='L222'></a><a href='#L222'>222</a>
-<a name='L223'></a><a href='#L223'>223</a>
-<a name='L224'></a><a href='#L224'>224</a>
-<a name='L225'></a><a href='#L225'>225</a>
-<a name='L226'></a><a href='#L226'>226</a>
-<a name='L227'></a><a href='#L227'>227</a>
-<a name='L228'></a><a href='#L228'>228</a>
-<a name='L229'></a><a href='#L229'>229</a>
-<a name='L230'></a><a href='#L230'>230</a>
-<a name='L231'></a><a href='#L231'>231</a>
-<a name='L232'></a><a href='#L232'>232</a>
-<a name='L233'></a><a href='#L233'>233</a>
-<a name='L234'></a><a href='#L234'>234</a>
-<a name='L235'></a><a href='#L235'>235</a>
-<a name='L236'></a><a href='#L236'>236</a>
-<a name='L237'></a><a href='#L237'>237</a>
-<a name='L238'></a><a href='#L238'>238</a>
-<a name='L239'></a><a href='#L239'>239</a>
-<a name='L240'></a><a href='#L240'>240</a>
-<a name='L241'></a><a href='#L241'>241</a>
-<a name='L242'></a><a href='#L242'>242</a>
-<a name='L243'></a><a href='#L243'>243</a>
-<a name='L244'></a><a href='#L244'>244</a>
-<a name='L245'></a><a href='#L245'>245</a>
-<a name='L246'></a><a href='#L246'>246</a>
-<a name='L247'></a><a href='#L247'>247</a>
-<a name='L248'></a><a href='#L248'>248</a>
-<a name='L249'></a><a href='#L249'>249</a>
-<a name='L250'></a><a href='#L250'>250</a>
-<a name='L251'></a><a href='#L251'>251</a>
-<a name='L252'></a><a href='#L252'>252</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">8x</span>
-<span class="cline-any cline-yes">8x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-yes">9x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">10x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">10x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">9x</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">6x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">9x</span>
-<span class="cline-any cline-yes">9x</span>
-<span class="cline-any cline-yes">9x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">8x</span>
-<span class="cline-any cline-yes">8x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">5x</span>
-<span class="cline-any cline-yes">5x</span>
-<span class="cline-any cline-yes">5x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-yes">6x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">6x</span>
-<span class="cline-any cline-yes">5x</span>
-<span class="cline-any cline-yes">5x</span>
-<span class="cline-any cline-yes">5x</span>
-<span class="cline-any cline-yes">5x</span>
-<span class="cline-any cline-yes">5x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">5x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">5x</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">5x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-yes">31x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">36x</span>
-<span class="cline-any cline-yes">21x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">20x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">44x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { useEffect, useState } from 'react';
-import { useParams, useSearchParams } from 'react-router-dom';
-import type { Item } from '../types';
-import FeedItem from './FeedItem';
-import './FeedItems.css';
-import { apiFetch } from '../utils';
-&nbsp;
-export default function FeedItems() {
- const { feedId, tagName } = useParams&lt;{ feedId: string; tagName: string }&gt;();
- const [searchParams] = useSearchParams();
- const filterFn = searchParams.get('filter') || 'unread';
-&nbsp;
- const [items, setItems] = useState&lt;Item[]&gt;([]);
- const [loading, setLoading] = useState(true);
- const [loadingMore, setLoadingMore] = useState(false);
- const [hasMore, setHasMore] = useState(true);
- const [error, setError] = useState('');
- const [selectedIndex, setSelectedIndex] = useState(-1);
-&nbsp;
- const fetchItems = (maxId?: string) =&gt; {
- if (maxId) {
- setLoadingMore(true);
- } else {
- setLoading(true);
- setItems([]);
- }
- setError('');
-&nbsp;
- let url = '/api/stream';
- const params = new URLSearchParams();
-&nbsp;
- if (feedId) {
- params.append('feed_id', feedId);
- } else if (tagName) {
- params.append('tag', tagName);
- }
-&nbsp;
- if (maxId) {
- params.append('max_id', maxId);
- }
-&nbsp;
- // Apply filters
- const searchQuery = searchParams.get('q');
- <span class="missing-if-branch" title="if path not taken" >I</span>if (searchQuery) {
-<span class="cstat-no" title="statement not covered" > params.append('q', searchQuery);</span>
- }
-&nbsp;
- <span class="missing-if-branch" title="if path not taken" >I</span>if (filterFn === 'all') {
-<span class="cstat-no" title="statement not covered" > params.append('read_filter', 'all');</span>
- <span class="missing-if-branch" title="if path not taken" >I</span>} else if (filterFn === 'starred') {
-<span class="cstat-no" title="statement not covered" > params.append('starred', 'true');</span>
-<span class="cstat-no" title="statement not covered" > params.append('read_filter', 'all');</span>
- } else {
- // default to unread
- <span class="missing-if-branch" title="else path not taken" >E</span>if (!searchQuery) {
- params.append('read_filter', 'unread');
- }
- }
-&nbsp;
- const queryString = params.toString();
- <span class="missing-if-branch" title="else path not taken" >E</span>if (queryString) {
- url += `?${queryString}`;
- }
-&nbsp;
- apiFetch(url)
- .then((res) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) {
-<span class="cstat-no" title="statement not covered" > throw new Error('Failed to fetch items');</span>
- }
- return res.json();
- })
- .then((data) =&gt; {
- if (maxId) {
- setItems((prev) =&gt; [...prev, ...data]);
- } else {
- setItems(data);
- }
- setHasMore(data.length &gt; 0);
- setLoading(false);
- setLoadingMore(false);
- })
- .catch((err) =&gt; {
- setError(err.message);
- setLoading(false);
- setLoadingMore(false);
- });
- };
-&nbsp;
- useEffect(() =&gt; {
- fetchItems();
- setSelectedIndex(-1);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [feedId, tagName, filterFn, searchParams]);
-&nbsp;
-&nbsp;
- const scrollToItem = (index: number) =&gt; {
- const element = document.getElementById(`item-${index}`);
- <span class="missing-if-branch" title="else path not taken" >E</span>if (element) {
- element.scrollIntoView({ behavior: 'auto', block: 'start' });
- }
- };
-&nbsp;
- const markAsRead = (item: Item) =&gt; {
- const updatedItem = { ...item, read: true };
- // Optimistic update
- setItems((prevItems) =&gt; prevItems.map((i) =&gt; (i._id === item._id ? updatedItem : i)));
-&nbsp;
- apiFetch(`/api/item/${item._id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ read: true, starred: item.starred }),
- }).catch(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; <span class="cstat-no" title="statement not covered" >console.error('Failed to mark read', err))</span>;
- };
-&nbsp;
- const toggleStar = (item: Item) =&gt; {
- const updatedItem = { ...item, starred: !item.starred };
- // Optimistic update
- setItems((prevItems) =&gt; prevItems.map((i) =&gt; (i._id === item._id ? updatedItem : i)));
-&nbsp;
- apiFetch(`/api/item/${item._id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ read: item.read, starred: !item.starred }),
- }).catch(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; <span class="cstat-no" title="statement not covered" >console.error('Failed to toggle star', err))</span>;
- };
-&nbsp;
- useEffect(() =&gt; {
- const handleKeyDown = (e: KeyboardEvent) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (items.length === 0) <span class="cstat-no" title="statement not covered" >return;</span>
-&nbsp;
- if (e.key === 'j') {
- setSelectedIndex((prev) =&gt; {
- const nextIndex = Math.min(prev + 1, items.length - 1);
- <span class="missing-if-branch" title="else path not taken" >E</span>if (nextIndex !== prev) {
- const item = items[nextIndex];
- if (!item.read) {
- markAsRead(item);
- }
- scrollToItem(nextIndex);
- }
-&nbsp;
- // If we're now on the last item and there are more items to load,
- // trigger loading them so the next 'j' press will work
- if (nextIndex === items.length - 1 &amp;&amp; hasMore &amp;&amp; !loadingMore) {
- fetchItems(String(items[items.length - 1]._id));
- }
-&nbsp;
- return nextIndex;
- });
- <span class="missing-if-branch" title="if path not taken" >I</span>} else if (e.key === 'k') {
-<span class="cstat-no" title="statement not covered" > setSelectedIndex(<span class="fstat-no" title="function not covered" >(p</span>rev) =&gt; {</span>
- const nextIndex = <span class="cstat-no" title="statement not covered" >Math.max(prev - 1, 0);</span>
-<span class="cstat-no" title="statement not covered" > if (nextIndex !== prev) {</span>
-<span class="cstat-no" title="statement not covered" > scrollToItem(nextIndex);</span>
- }
-<span class="cstat-no" title="statement not covered" > return nextIndex;</span>
- });
- <span class="missing-if-branch" title="else path not taken" >E</span>} else if (e.key === 's') {
- setSelectedIndex((currentIndex) =&gt; {
- <span class="missing-if-branch" title="else path not taken" >E</span>if (currentIndex &gt;= 0 &amp;&amp; currentIndex &lt; items.length) {
- toggleStar(items[currentIndex]);
- }
- return currentIndex;
- });
- }
- };
-&nbsp;
- window.addEventListener('keydown', handleKeyDown);
- return () =&gt; window.removeEventListener('keydown', handleKeyDown);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [items, hasMore, loadingMore]);
-&nbsp;
-&nbsp;
-&nbsp;
- useEffect(() =&gt; {
- // Observer for marking items as read
- const itemObserver = new IntersectionObserver(
- (entries) =&gt; {
- entries.forEach((entry) =&gt; {
- // If item is not intersecting and is above the viewport, it's been scrolled past
- <span class="missing-if-branch" title="else path not taken" >E</span>if (!entry.isIntersecting &amp;&amp; entry.boundingClientRect.top &lt; 0) {
- const index = Number(entry.target.getAttribute('data-index'));
- <span class="missing-if-branch" title="else path not taken" >E</span>if (!isNaN(index) &amp;&amp; index &gt;= 0 &amp;&amp; index &lt; items.length) {
- const item = items[index];
- <span class="missing-if-branch" title="else path not taken" >E</span>if (!item.read) {
- markAsRead(item);
- }
- }
- }
- });
- },
- { root: null, threshold: 0 }
- );
-&nbsp;
- // Observer for infinite scroll (less aggressive, must be fully visible)
- const sentinelObserver = new IntersectionObserver(
- (entries) =&gt; {
- entries.forEach((entry) =&gt; {
- <span class="missing-if-branch" title="else path not taken" >E</span>if (entry.isIntersecting &amp;&amp; !loadingMore &amp;&amp; hasMore &amp;&amp; items.length &gt; 0) {
- fetchItems(String(items[items.length - 1]._id));
- }
- });
- },
- { root: null, threshold: 1.0 }
- );
-&nbsp;
- items.forEach((_, index) =&gt; {
- const el = document.getElementById(`item-${index}`);
- <span class="missing-if-branch" title="else path not taken" >E</span>if (el) itemObserver.observe(el);
- });
-&nbsp;
- const sentinel = document.getElementById('load-more-sentinel');
- if (sentinel) sentinelObserver.observe(sentinel);
-&nbsp;
- return () =&gt; {
- itemObserver.disconnect();
- sentinelObserver.disconnect();
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [items, loadingMore, hasMore]);
-&nbsp;
- if (loading) return &lt;div className="feed-items-loading"&gt;Loading items...&lt;/div&gt;;
- if (error) return &lt;div className="feed-items-error"&gt;Error: {error}&lt;/div&gt;;
-&nbsp;
- return (
- &lt;div className="feed-items"&gt;
- {items.length === 0 ? (
-<span class="branch-0 cbranch-no" title="branch not covered" > &lt;p&gt;No items found.&lt;/p&gt;</span>
- ) : (
- &lt;ul className="item-list"&gt;
- {items.map((item, index) =&gt; (
- &lt;div
- id={`item-${index}`}
- key={item._id}
- data-index={index}
- data-selected={index === selectedIndex}
- onClick={<span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >s</span>etSelectedIndex(index)}</span>
- &gt;
- &lt;FeedItem item={item} /&gt;
- &lt;/div&gt;
- ))}
- {hasMore &amp;&amp; (
- &lt;div id="load-more-sentinel" className="loading-more"&gt;
- {loadingMore ? 'Loading more...' : ''}
- &lt;/div&gt;
- )}
- &lt;/ul&gt;
- )}
- &lt;/div&gt;
- );
-}
-&nbsp;</pre></td></tr></table></pre>
-
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../../sorter.js"></script>
- <script src="../../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/components/FeedList.css.html b/frontend/coverage/src/components/FeedList.css.html
deleted file mode 100644
index d892e77..0000000
--- a/frontend/coverage/src/components/FeedList.css.html
+++ /dev/null
@@ -1,724 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/components/FeedList.css</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../../prettify.css" />
- <link rel="stylesheet" href="../../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedList.css</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line low'></div>
- <pre><table class="coverage">
-<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
-<a name='L2'></a><a href='#L2'>2</a>
-<a name='L3'></a><a href='#L3'>3</a>
-<a name='L4'></a><a href='#L4'>4</a>
-<a name='L5'></a><a href='#L5'>5</a>
-<a name='L6'></a><a href='#L6'>6</a>
-<a name='L7'></a><a href='#L7'>7</a>
-<a name='L8'></a><a href='#L8'>8</a>
-<a name='L9'></a><a href='#L9'>9</a>
-<a name='L10'></a><a href='#L10'>10</a>
-<a name='L11'></a><a href='#L11'>11</a>
-<a name='L12'></a><a href='#L12'>12</a>
-<a name='L13'></a><a href='#L13'>13</a>
-<a name='L14'></a><a href='#L14'>14</a>
-<a name='L15'></a><a href='#L15'>15</a>
-<a name='L16'></a><a href='#L16'>16</a>
-<a name='L17'></a><a href='#L17'>17</a>
-<a name='L18'></a><a href='#L18'>18</a>
-<a name='L19'></a><a href='#L19'>19</a>
-<a name='L20'></a><a href='#L20'>20</a>
-<a name='L21'></a><a href='#L21'>21</a>
-<a name='L22'></a><a href='#L22'>22</a>
-<a name='L23'></a><a href='#L23'>23</a>
-<a name='L24'></a><a href='#L24'>24</a>
-<a name='L25'></a><a href='#L25'>25</a>
-<a name='L26'></a><a href='#L26'>26</a>
-<a name='L27'></a><a href='#L27'>27</a>
-<a name='L28'></a><a href='#L28'>28</a>
-<a name='L29'></a><a href='#L29'>29</a>
-<a name='L30'></a><a href='#L30'>30</a>
-<a name='L31'></a><a href='#L31'>31</a>
-<a name='L32'></a><a href='#L32'>32</a>
-<a name='L33'></a><a href='#L33'>33</a>
-<a name='L34'></a><a href='#L34'>34</a>
-<a name='L35'></a><a href='#L35'>35</a>
-<a name='L36'></a><a href='#L36'>36</a>
-<a name='L37'></a><a href='#L37'>37</a>
-<a name='L38'></a><a href='#L38'>38</a>
-<a name='L39'></a><a href='#L39'>39</a>
-<a name='L40'></a><a href='#L40'>40</a>
-<a name='L41'></a><a href='#L41'>41</a>
-<a name='L42'></a><a href='#L42'>42</a>
-<a name='L43'></a><a href='#L43'>43</a>
-<a name='L44'></a><a href='#L44'>44</a>
-<a name='L45'></a><a href='#L45'>45</a>
-<a name='L46'></a><a href='#L46'>46</a>
-<a name='L47'></a><a href='#L47'>47</a>
-<a name='L48'></a><a href='#L48'>48</a>
-<a name='L49'></a><a href='#L49'>49</a>
-<a name='L50'></a><a href='#L50'>50</a>
-<a name='L51'></a><a href='#L51'>51</a>
-<a name='L52'></a><a href='#L52'>52</a>
-<a name='L53'></a><a href='#L53'>53</a>
-<a name='L54'></a><a href='#L54'>54</a>
-<a name='L55'></a><a href='#L55'>55</a>
-<a name='L56'></a><a href='#L56'>56</a>
-<a name='L57'></a><a href='#L57'>57</a>
-<a name='L58'></a><a href='#L58'>58</a>
-<a name='L59'></a><a href='#L59'>59</a>
-<a name='L60'></a><a href='#L60'>60</a>
-<a name='L61'></a><a href='#L61'>61</a>
-<a name='L62'></a><a href='#L62'>62</a>
-<a name='L63'></a><a href='#L63'>63</a>
-<a name='L64'></a><a href='#L64'>64</a>
-<a name='L65'></a><a href='#L65'>65</a>
-<a name='L66'></a><a href='#L66'>66</a>
-<a name='L67'></a><a href='#L67'>67</a>
-<a name='L68'></a><a href='#L68'>68</a>
-<a name='L69'></a><a href='#L69'>69</a>
-<a name='L70'></a><a href='#L70'>70</a>
-<a name='L71'></a><a href='#L71'>71</a>
-<a name='L72'></a><a href='#L72'>72</a>
-<a name='L73'></a><a href='#L73'>73</a>
-<a name='L74'></a><a href='#L74'>74</a>
-<a name='L75'></a><a href='#L75'>75</a>
-<a name='L76'></a><a href='#L76'>76</a>
-<a name='L77'></a><a href='#L77'>77</a>
-<a name='L78'></a><a href='#L78'>78</a>
-<a name='L79'></a><a href='#L79'>79</a>
-<a name='L80'></a><a href='#L80'>80</a>
-<a name='L81'></a><a href='#L81'>81</a>
-<a name='L82'></a><a href='#L82'>82</a>
-<a name='L83'></a><a href='#L83'>83</a>
-<a name='L84'></a><a href='#L84'>84</a>
-<a name='L85'></a><a href='#L85'>85</a>
-<a name='L86'></a><a href='#L86'>86</a>
-<a name='L87'></a><a href='#L87'>87</a>
-<a name='L88'></a><a href='#L88'>88</a>
-<a name='L89'></a><a href='#L89'>89</a>
-<a name='L90'></a><a href='#L90'>90</a>
-<a name='L91'></a><a href='#L91'>91</a>
-<a name='L92'></a><a href='#L92'>92</a>
-<a name='L93'></a><a href='#L93'>93</a>
-<a name='L94'></a><a href='#L94'>94</a>
-<a name='L95'></a><a href='#L95'>95</a>
-<a name='L96'></a><a href='#L96'>96</a>
-<a name='L97'></a><a href='#L97'>97</a>
-<a name='L98'></a><a href='#L98'>98</a>
-<a name='L99'></a><a href='#L99'>99</a>
-<a name='L100'></a><a href='#L100'>100</a>
-<a name='L101'></a><a href='#L101'>101</a>
-<a name='L102'></a><a href='#L102'>102</a>
-<a name='L103'></a><a href='#L103'>103</a>
-<a name='L104'></a><a href='#L104'>104</a>
-<a name='L105'></a><a href='#L105'>105</a>
-<a name='L106'></a><a href='#L106'>106</a>
-<a name='L107'></a><a href='#L107'>107</a>
-<a name='L108'></a><a href='#L108'>108</a>
-<a name='L109'></a><a href='#L109'>109</a>
-<a name='L110'></a><a href='#L110'>110</a>
-<a name='L111'></a><a href='#L111'>111</a>
-<a name='L112'></a><a href='#L112'>112</a>
-<a name='L113'></a><a href='#L113'>113</a>
-<a name='L114'></a><a href='#L114'>114</a>
-<a name='L115'></a><a href='#L115'>115</a>
-<a name='L116'></a><a href='#L116'>116</a>
-<a name='L117'></a><a href='#L117'>117</a>
-<a name='L118'></a><a href='#L118'>118</a>
-<a name='L119'></a><a href='#L119'>119</a>
-<a name='L120'></a><a href='#L120'>120</a>
-<a name='L121'></a><a href='#L121'>121</a>
-<a name='L122'></a><a href='#L122'>122</a>
-<a name='L123'></a><a href='#L123'>123</a>
-<a name='L124'></a><a href='#L124'>124</a>
-<a name='L125'></a><a href='#L125'>125</a>
-<a name='L126'></a><a href='#L126'>126</a>
-<a name='L127'></a><a href='#L127'>127</a>
-<a name='L128'></a><a href='#L128'>128</a>
-<a name='L129'></a><a href='#L129'>129</a>
-<a name='L130'></a><a href='#L130'>130</a>
-<a name='L131'></a><a href='#L131'>131</a>
-<a name='L132'></a><a href='#L132'>132</a>
-<a name='L133'></a><a href='#L133'>133</a>
-<a name='L134'></a><a href='#L134'>134</a>
-<a name='L135'></a><a href='#L135'>135</a>
-<a name='L136'></a><a href='#L136'>136</a>
-<a name='L137'></a><a href='#L137'>137</a>
-<a name='L138'></a><a href='#L138'>138</a>
-<a name='L139'></a><a href='#L139'>139</a>
-<a name='L140'></a><a href='#L140'>140</a>
-<a name='L141'></a><a href='#L141'>141</a>
-<a name='L142'></a><a href='#L142'>142</a>
-<a name='L143'></a><a href='#L143'>143</a>
-<a name='L144'></a><a href='#L144'>144</a>
-<a name='L145'></a><a href='#L145'>145</a>
-<a name='L146'></a><a href='#L146'>146</a>
-<a name='L147'></a><a href='#L147'>147</a>
-<a name='L148'></a><a href='#L148'>148</a>
-<a name='L149'></a><a href='#L149'>149</a>
-<a name='L150'></a><a href='#L150'>150</a>
-<a name='L151'></a><a href='#L151'>151</a>
-<a name='L152'></a><a href='#L152'>152</a>
-<a name='L153'></a><a href='#L153'>153</a>
-<a name='L154'></a><a href='#L154'>154</a>
-<a name='L155'></a><a href='#L155'>155</a>
-<a name='L156'></a><a href='#L156'>156</a>
-<a name='L157'></a><a href='#L157'>157</a>
-<a name='L158'></a><a href='#L158'>158</a>
-<a name='L159'></a><a href='#L159'>159</a>
-<a name='L160'></a><a href='#L160'>160</a>
-<a name='L161'></a><a href='#L161'>161</a>
-<a name='L162'></a><a href='#L162'>162</a>
-<a name='L163'></a><a href='#L163'>163</a>
-<a name='L164'></a><a href='#L164'>164</a>
-<a name='L165'></a><a href='#L165'>165</a>
-<a name='L166'></a><a href='#L166'>166</a>
-<a name='L167'></a><a href='#L167'>167</a>
-<a name='L168'></a><a href='#L168'>168</a>
-<a name='L169'></a><a href='#L169'>169</a>
-<a name='L170'></a><a href='#L170'>170</a>
-<a name='L171'></a><a href='#L171'>171</a>
-<a name='L172'></a><a href='#L172'>172</a>
-<a name='L173'></a><a href='#L173'>173</a>
-<a name='L174'></a><a href='#L174'>174</a>
-<a name='L175'></a><a href='#L175'>175</a>
-<a name='L176'></a><a href='#L176'>176</a>
-<a name='L177'></a><a href='#L177'>177</a>
-<a name='L178'></a><a href='#L178'>178</a>
-<a name='L179'></a><a href='#L179'>179</a>
-<a name='L180'></a><a href='#L180'>180</a>
-<a name='L181'></a><a href='#L181'>181</a>
-<a name='L182'></a><a href='#L182'>182</a>
-<a name='L183'></a><a href='#L183'>183</a>
-<a name='L184'></a><a href='#L184'>184</a>
-<a name='L185'></a><a href='#L185'>185</a>
-<a name='L186'></a><a href='#L186'>186</a>
-<a name='L187'></a><a href='#L187'>187</a>
-<a name='L188'></a><a href='#L188'>188</a>
-<a name='L189'></a><a href='#L189'>189</a>
-<a name='L190'></a><a href='#L190'>190</a>
-<a name='L191'></a><a href='#L191'>191</a>
-<a name='L192'></a><a href='#L192'>192</a>
-<a name='L193'></a><a href='#L193'>193</a>
-<a name='L194'></a><a href='#L194'>194</a>
-<a name='L195'></a><a href='#L195'>195</a>
-<a name='L196'></a><a href='#L196'>196</a>
-<a name='L197'></a><a href='#L197'>197</a>
-<a name='L198'></a><a href='#L198'>198</a>
-<a name='L199'></a><a href='#L199'>199</a>
-<a name='L200'></a><a href='#L200'>200</a>
-<a name='L201'></a><a href='#L201'>201</a>
-<a name='L202'></a><a href='#L202'>202</a>
-<a name='L203'></a><a href='#L203'>203</a>
-<a name='L204'></a><a href='#L204'>204</a>
-<a name='L205'></a><a href='#L205'>205</a>
-<a name='L206'></a><a href='#L206'>206</a>
-<a name='L207'></a><a href='#L207'>207</a>
-<a name='L208'></a><a href='#L208'>208</a>
-<a name='L209'></a><a href='#L209'>209</a>
-<a name='L210'></a><a href='#L210'>210</a>
-<a name='L211'></a><a href='#L211'>211</a>
-<a name='L212'></a><a href='#L212'>212</a>
-<a name='L213'></a><a href='#L213'>213</a>
-<a name='L214'></a><a href='#L214'>214</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">.feed-list {
- padding: 1rem;
- font-family: var(--font-heading);
- color: #777;
- /* specific v1 color */
- font-size: 0.8rem;
- background: var(--sidebar-bg);
- min-height: 100%;
- flex: 1;
-}
-&nbsp;
-.feed-list h1.logo {
- font-size: 2rem;
- /* match v1 */
- margin: 0 0 1rem 0;
- line-height: 1;
- cursor: pointer;
- position: sticky;
- top: 0;
- background: var(--sidebar-bg);
- z-index: 10;
- padding-bottom: 0.5rem;
- color: var(--text-color);
-}
-&nbsp;
-/* Override logo color if necessary for themes */
-.theme-light .feed-list h1.logo {
- color: #333;
-}
-&nbsp;
-.theme-dark .feed-list h1.logo {
- color: #eee;
-}
-&nbsp;
-.search-section {
- margin-bottom: 1rem;
-}
-&nbsp;
-.search-input {
- width: 100%;
- padding: 0.25rem;
- border: 1px solid var(--border-color, #999);
- background: var(--bg-color);
- color: var(--text-color);
- font-size: 0.8rem;
- font-family: inherit;
- border-radius: 0;
- /* v1 didn't have rounded inputs usually */
-}
-&nbsp;
-.section-header {
- font-size: 1rem;
- /* v1 h4 size? */
- font-weight: bold;
- margin: 1rem 0 0.25rem 0;
- cursor: pointer;
- user-select: none;
- font-family: var(--font-heading);
- color: #333;
- /* Darker than list items */
- text-transform: lowercase;
- font-variant: small-caps;
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-&nbsp;
-.caret {
- display: inline-block;
- font-size: 0.6rem;
- transition: transform 0.2s ease;
- color: #777;
-}
-&nbsp;
-.caret.expanded {
- transform: rotate(90deg);
-}
-&nbsp;
-.filter-list,
-.tag-list-items,
-.feed-list-items,
-.nav-list {
- list-style: none;
- padding: 0;
- margin: 0;
-}
-&nbsp;
-.filter-list li,
-.nav-list li {
- margin-bottom: 0.1rem;
-}
-&nbsp;
-.filter-list a,
-.nav-list a,
-.tag-link,
-.feed-title,
-.logout-link {
- text-decoration: none;
- color: var(--link-color, blue);
- font-size: 0.8rem;
- /* Matches v1 .75em approx */
- display: block;
- cursor: pointer;
- background: none;
- border: none;
- padding: 0;
- font-family: inherit;
- font-variant: small-caps;
- text-transform: lowercase;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-&nbsp;
-.filter-list a:hover,
-.nav-list a:hover,
-.tag-link:hover,
-.feed-title:hover,
-.logout-link:hover {
- text-decoration: underline;
- color: var(--link-color, blue);
-}
-&nbsp;
-.filter-list a.active,
-.tag-link.active,
-.feed-title.active {
- font-weight: bold;
- color: #000;
- /* Active state black */
-}
-&nbsp;
-.tag-item,
-.sidebar-feed-item {
- margin-bottom: 0;
-}
-&nbsp;
-.feed-category {
- display: none;
-}
-&nbsp;
-.nav-section {
- margin-top: 2rem;
- border-top: 1px solid var(--border-color, #eee);
- padding-top: 1rem;
-}
-&nbsp;
-.logout-link {
- text-align: left;
- width: 100%;
- color: #777;
- display: block;
-}
-&nbsp;
-.nav-link,
-.logout-link {
- padding: 0.25rem 0;
-}
-&nbsp;
-.logout-link:hover {
- color: var(--link-color, blue);
- text-decoration: underline;
-}
-&nbsp;
-.theme-section {
- margin-top: 1rem;
-}
-&nbsp;
-.theme-selector {
- display: flex;
- gap: 0.5rem;
- margin-top: 0.5rem;
-}
-&nbsp;
-.theme-selector button {
- background: rgba(0, 0, 0, 0.05);
- border: none;
- cursor: pointer;
- padding: 0.4rem;
- font-size: 1rem;
- border-radius: 8px;
- line-height: 1;
- transition: all 0.2s ease;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-&nbsp;
-.theme-selector button:hover {
- background: rgba(0, 0, 0, 0.1);
- transform: translateY(-2px);
-}
-&nbsp;
-.theme-selector button.active {
- background: var(--border-color, #999);
- color: white;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
-}
-&nbsp;
-.theme-dark .theme-selector button {
- background: rgba(255, 255, 255, 0.1);
-}
-&nbsp;
-.theme-dark .theme-selector button:hover {
- background: rgba(255, 255, 255, 0.2);
-}
-&nbsp;
-/* Scrollbar styling for webkit */
-.dashboard-sidebar::-webkit-scrollbar {
- width: 4px;
-}
-&nbsp;
-.dashboard-sidebar::-webkit-scrollbar-thumb {
- background-color: var(--border-color, #999);
-}</pre></td></tr></table></pre>
-
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../../sorter.js"></script>
- <script src="../../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/components/FeedList.tsx.html b/frontend/coverage/src/components/FeedList.tsx.html
deleted file mode 100644
index 4061422..0000000
--- a/frontend/coverage/src/components/FeedList.tsx.html
+++ /dev/null
@@ -1,721 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/components/FeedList.tsx</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../../prettify.css" />
- <link rel="stylesheet" href="../../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedList.tsx</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">87.27% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>48/55</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">70% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>35/50</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">78.94% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>15/19</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">90% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>45/50</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line high'></div>
- <pre><table class="coverage">
-<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
-<a name='L2'></a><a href='#L2'>2</a>
-<a name='L3'></a><a href='#L3'>3</a>
-<a name='L4'></a><a href='#L4'>4</a>
-<a name='L5'></a><a href='#L5'>5</a>
-<a name='L6'></a><a href='#L6'>6</a>
-<a name='L7'></a><a href='#L7'>7</a>
-<a name='L8'></a><a href='#L8'>8</a>
-<a name='L9'></a><a href='#L9'>9</a>
-<a name='L10'></a><a href='#L10'>10</a>
-<a name='L11'></a><a href='#L11'>11</a>
-<a name='L12'></a><a href='#L12'>12</a>
-<a name='L13'></a><a href='#L13'>13</a>
-<a name='L14'></a><a href='#L14'>14</a>
-<a name='L15'></a><a href='#L15'>15</a>
-<a name='L16'></a><a href='#L16'>16</a>
-<a name='L17'></a><a href='#L17'>17</a>
-<a name='L18'></a><a href='#L18'>18</a>
-<a name='L19'></a><a href='#L19'>19</a>
-<a name='L20'></a><a href='#L20'>20</a>
-<a name='L21'></a><a href='#L21'>21</a>
-<a name='L22'></a><a href='#L22'>22</a>
-<a name='L23'></a><a href='#L23'>23</a>
-<a name='L24'></a><a href='#L24'>24</a>
-<a name='L25'></a><a href='#L25'>25</a>
-<a name='L26'></a><a href='#L26'>26</a>
-<a name='L27'></a><a href='#L27'>27</a>
-<a name='L28'></a><a href='#L28'>28</a>
-<a name='L29'></a><a href='#L29'>29</a>
-<a name='L30'></a><a href='#L30'>30</a>
-<a name='L31'></a><a href='#L31'>31</a>
-<a name='L32'></a><a href='#L32'>32</a>
-<a name='L33'></a><a href='#L33'>33</a>
-<a name='L34'></a><a href='#L34'>34</a>
-<a name='L35'></a><a href='#L35'>35</a>
-<a name='L36'></a><a href='#L36'>36</a>
-<a name='L37'></a><a href='#L37'>37</a>
-<a name='L38'></a><a href='#L38'>38</a>
-<a name='L39'></a><a href='#L39'>39</a>
-<a name='L40'></a><a href='#L40'>40</a>
-<a name='L41'></a><a href='#L41'>41</a>
-<a name='L42'></a><a href='#L42'>42</a>
-<a name='L43'></a><a href='#L43'>43</a>
-<a name='L44'></a><a href='#L44'>44</a>
-<a name='L45'></a><a href='#L45'>45</a>
-<a name='L46'></a><a href='#L46'>46</a>
-<a name='L47'></a><a href='#L47'>47</a>
-<a name='L48'></a><a href='#L48'>48</a>
-<a name='L49'></a><a href='#L49'>49</a>
-<a name='L50'></a><a href='#L50'>50</a>
-<a name='L51'></a><a href='#L51'>51</a>
-<a name='L52'></a><a href='#L52'>52</a>
-<a name='L53'></a><a href='#L53'>53</a>
-<a name='L54'></a><a href='#L54'>54</a>
-<a name='L55'></a><a href='#L55'>55</a>
-<a name='L56'></a><a href='#L56'>56</a>
-<a name='L57'></a><a href='#L57'>57</a>
-<a name='L58'></a><a href='#L58'>58</a>
-<a name='L59'></a><a href='#L59'>59</a>
-<a name='L60'></a><a href='#L60'>60</a>
-<a name='L61'></a><a href='#L61'>61</a>
-<a name='L62'></a><a href='#L62'>62</a>
-<a name='L63'></a><a href='#L63'>63</a>
-<a name='L64'></a><a href='#L64'>64</a>
-<a name='L65'></a><a href='#L65'>65</a>
-<a name='L66'></a><a href='#L66'>66</a>
-<a name='L67'></a><a href='#L67'>67</a>
-<a name='L68'></a><a href='#L68'>68</a>
-<a name='L69'></a><a href='#L69'>69</a>
-<a name='L70'></a><a href='#L70'>70</a>
-<a name='L71'></a><a href='#L71'>71</a>
-<a name='L72'></a><a href='#L72'>72</a>
-<a name='L73'></a><a href='#L73'>73</a>
-<a name='L74'></a><a href='#L74'>74</a>
-<a name='L75'></a><a href='#L75'>75</a>
-<a name='L76'></a><a href='#L76'>76</a>
-<a name='L77'></a><a href='#L77'>77</a>
-<a name='L78'></a><a href='#L78'>78</a>
-<a name='L79'></a><a href='#L79'>79</a>
-<a name='L80'></a><a href='#L80'>80</a>
-<a name='L81'></a><a href='#L81'>81</a>
-<a name='L82'></a><a href='#L82'>82</a>
-<a name='L83'></a><a href='#L83'>83</a>
-<a name='L84'></a><a href='#L84'>84</a>
-<a name='L85'></a><a href='#L85'>85</a>
-<a name='L86'></a><a href='#L86'>86</a>
-<a name='L87'></a><a href='#L87'>87</a>
-<a name='L88'></a><a href='#L88'>88</a>
-<a name='L89'></a><a href='#L89'>89</a>
-<a name='L90'></a><a href='#L90'>90</a>
-<a name='L91'></a><a href='#L91'>91</a>
-<a name='L92'></a><a href='#L92'>92</a>
-<a name='L93'></a><a href='#L93'>93</a>
-<a name='L94'></a><a href='#L94'>94</a>
-<a name='L95'></a><a href='#L95'>95</a>
-<a name='L96'></a><a href='#L96'>96</a>
-<a name='L97'></a><a href='#L97'>97</a>
-<a name='L98'></a><a href='#L98'>98</a>
-<a name='L99'></a><a href='#L99'>99</a>
-<a name='L100'></a><a href='#L100'>100</a>
-<a name='L101'></a><a href='#L101'>101</a>
-<a name='L102'></a><a href='#L102'>102</a>
-<a name='L103'></a><a href='#L103'>103</a>
-<a name='L104'></a><a href='#L104'>104</a>
-<a name='L105'></a><a href='#L105'>105</a>
-<a name='L106'></a><a href='#L106'>106</a>
-<a name='L107'></a><a href='#L107'>107</a>
-<a name='L108'></a><a href='#L108'>108</a>
-<a name='L109'></a><a href='#L109'>109</a>
-<a name='L110'></a><a href='#L110'>110</a>
-<a name='L111'></a><a href='#L111'>111</a>
-<a name='L112'></a><a href='#L112'>112</a>
-<a name='L113'></a><a href='#L113'>113</a>
-<a name='L114'></a><a href='#L114'>114</a>
-<a name='L115'></a><a href='#L115'>115</a>
-<a name='L116'></a><a href='#L116'>116</a>
-<a name='L117'></a><a href='#L117'>117</a>
-<a name='L118'></a><a href='#L118'>118</a>
-<a name='L119'></a><a href='#L119'>119</a>
-<a name='L120'></a><a href='#L120'>120</a>
-<a name='L121'></a><a href='#L121'>121</a>
-<a name='L122'></a><a href='#L122'>122</a>
-<a name='L123'></a><a href='#L123'>123</a>
-<a name='L124'></a><a href='#L124'>124</a>
-<a name='L125'></a><a href='#L125'>125</a>
-<a name='L126'></a><a href='#L126'>126</a>
-<a name='L127'></a><a href='#L127'>127</a>
-<a name='L128'></a><a href='#L128'>128</a>
-<a name='L129'></a><a href='#L129'>129</a>
-<a name='L130'></a><a href='#L130'>130</a>
-<a name='L131'></a><a href='#L131'>131</a>
-<a name='L132'></a><a href='#L132'>132</a>
-<a name='L133'></a><a href='#L133'>133</a>
-<a name='L134'></a><a href='#L134'>134</a>
-<a name='L135'></a><a href='#L135'>135</a>
-<a name='L136'></a><a href='#L136'>136</a>
-<a name='L137'></a><a href='#L137'>137</a>
-<a name='L138'></a><a href='#L138'>138</a>
-<a name='L139'></a><a href='#L139'>139</a>
-<a name='L140'></a><a href='#L140'>140</a>
-<a name='L141'></a><a href='#L141'>141</a>
-<a name='L142'></a><a href='#L142'>142</a>
-<a name='L143'></a><a href='#L143'>143</a>
-<a name='L144'></a><a href='#L144'>144</a>
-<a name='L145'></a><a href='#L145'>145</a>
-<a name='L146'></a><a href='#L146'>146</a>
-<a name='L147'></a><a href='#L147'>147</a>
-<a name='L148'></a><a href='#L148'>148</a>
-<a name='L149'></a><a href='#L149'>149</a>
-<a name='L150'></a><a href='#L150'>150</a>
-<a name='L151'></a><a href='#L151'>151</a>
-<a name='L152'></a><a href='#L152'>152</a>
-<a name='L153'></a><a href='#L153'>153</a>
-<a name='L154'></a><a href='#L154'>154</a>
-<a name='L155'></a><a href='#L155'>155</a>
-<a name='L156'></a><a href='#L156'>156</a>
-<a name='L157'></a><a href='#L157'>157</a>
-<a name='L158'></a><a href='#L158'>158</a>
-<a name='L159'></a><a href='#L159'>159</a>
-<a name='L160'></a><a href='#L160'>160</a>
-<a name='L161'></a><a href='#L161'>161</a>
-<a name='L162'></a><a href='#L162'>162</a>
-<a name='L163'></a><a href='#L163'>163</a>
-<a name='L164'></a><a href='#L164'>164</a>
-<a name='L165'></a><a href='#L165'>165</a>
-<a name='L166'></a><a href='#L166'>166</a>
-<a name='L167'></a><a href='#L167'>167</a>
-<a name='L168'></a><a href='#L168'>168</a>
-<a name='L169'></a><a href='#L169'>169</a>
-<a name='L170'></a><a href='#L170'>170</a>
-<a name='L171'></a><a href='#L171'>171</a>
-<a name='L172'></a><a href='#L172'>172</a>
-<a name='L173'></a><a href='#L173'>173</a>
-<a name='L174'></a><a href='#L174'>174</a>
-<a name='L175'></a><a href='#L175'>175</a>
-<a name='L176'></a><a href='#L176'>176</a>
-<a name='L177'></a><a href='#L177'>177</a>
-<a name='L178'></a><a href='#L178'>178</a>
-<a name='L179'></a><a href='#L179'>179</a>
-<a name='L180'></a><a href='#L180'>180</a>
-<a name='L181'></a><a href='#L181'>181</a>
-<a name='L182'></a><a href='#L182'>182</a>
-<a name='L183'></a><a href='#L183'>183</a>
-<a name='L184'></a><a href='#L184'>184</a>
-<a name='L185'></a><a href='#L185'>185</a>
-<a name='L186'></a><a href='#L186'>186</a>
-<a name='L187'></a><a href='#L187'>187</a>
-<a name='L188'></a><a href='#L188'>188</a>
-<a name='L189'></a><a href='#L189'>189</a>
-<a name='L190'></a><a href='#L190'>190</a>
-<a name='L191'></a><a href='#L191'>191</a>
-<a name='L192'></a><a href='#L192'>192</a>
-<a name='L193'></a><a href='#L193'>193</a>
-<a name='L194'></a><a href='#L194'>194</a>
-<a name='L195'></a><a href='#L195'>195</a>
-<a name='L196'></a><a href='#L196'>196</a>
-<a name='L197'></a><a href='#L197'>197</a>
-<a name='L198'></a><a href='#L198'>198</a>
-<a name='L199'></a><a href='#L199'>199</a>
-<a name='L200'></a><a href='#L200'>200</a>
-<a name='L201'></a><a href='#L201'>201</a>
-<a name='L202'></a><a href='#L202'>202</a>
-<a name='L203'></a><a href='#L203'>203</a>
-<a name='L204'></a><a href='#L204'>204</a>
-<a name='L205'></a><a href='#L205'>205</a>
-<a name='L206'></a><a href='#L206'>206</a>
-<a name='L207'></a><a href='#L207'>207</a>
-<a name='L208'></a><a href='#L208'>208</a>
-<a name='L209'></a><a href='#L209'>209</a>
-<a name='L210'></a><a href='#L210'>210</a>
-<a name='L211'></a><a href='#L211'>211</a>
-<a name='L212'></a><a href='#L212'>212</a>
-<a name='L213'></a><a href='#L213'>213</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-yes">11x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">9x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">7x</span>
-<span class="cline-any cline-yes">7x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">7x</span>
-<span class="cline-any cline-yes">7x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">7x</span>
-<span class="cline-any cline-yes">7x</span>
-<span class="cline-any cline-yes">7x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">22x</span>
-<span class="cline-any cline-yes">13x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">12x</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">12x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">4x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { useEffect, useState } from 'react';
-import { Link, useNavigate, useSearchParams, useLocation, useParams } from 'react-router-dom';
-import type { Feed, Category } from '../types';
-import './FeedList.css';
-import './FeedListVariants.css';
-import { apiFetch } from '../utils';
-&nbsp;
-export default function FeedList({
- theme,
- setTheme,
- setSidebarVisible,
- isMobile,
-}: {
- theme: string;
- setTheme: (t: string) =&gt; void;
- setSidebarVisible: (visible: boolean) =&gt; void;
- isMobile: boolean;
-}) {
- const [feeds, setFeeds] = useState&lt;Feed[]&gt;([]);
- const [tags, setTags] = useState&lt;Category[]&gt;([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState('');
- const [feedsExpanded, setFeedsExpanded] = useState(false);
- const [tagsExpanded, setTagsExpanded] = useState(true);
- const [searchQuery, setSearchQuery] = useState('');
- const navigate = useNavigate();
- const [searchParams] = useSearchParams();
- const location = useLocation();
- const { feedId, tagName } = useParams();
-&nbsp;
- const sidebarVariant = searchParams.get('sidebar') || localStorage.getItem('neko-sidebar-variant') || 'glass';
-&nbsp;
- useEffect(() =&gt; {
- const variant = searchParams.get('sidebar');
- <span class="missing-if-branch" title="if path not taken" >I</span>if (variant) {
-<span class="cstat-no" title="statement not covered" > localStorage.setItem('neko-sidebar-variant', variant);</span>
- }
- }, [searchParams]);
-&nbsp;
- const currentFilter =
- searchParams.get('filter') ||
- (location.pathname === '/' &amp;&amp; !feedId &amp;&amp; !tagName ? 'unread' : <span class="branch-1 cbranch-no" title="branch not covered" >'');</span>
-&nbsp;
- const handleSearch = (e: React.FormEvent) =&gt; {
- e.preventDefault();
- <span class="missing-if-branch" title="else path not taken" >E</span>if (searchQuery.trim()) {
- navigate(`/?q=${encodeURIComponent(searchQuery.trim())}`);
- }
- };
-&nbsp;
- const toggleFeeds = () =&gt; {
- setFeedsExpanded(!feedsExpanded);
- };
-&nbsp;
- const toggleTags = <span class="fstat-no" title="function not covered" >() =&gt; {</span>
-<span class="cstat-no" title="statement not covered" > setTagsExpanded(!tagsExpanded);</span>
- };
-&nbsp;
- const handleLinkClick = () =&gt; {
- <span class="missing-if-branch" title="else path not taken" >E</span>if (isMobile) {
- setSidebarVisible(false);
- }
- };
-&nbsp;
- useEffect(() =&gt; {
- Promise.all([
- apiFetch('/api/feed/').then((res) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to fetch feeds');</span>
- return res.json() as Promise&lt;Feed[]&gt;;
- }),
- apiFetch('/api/tag').then((res) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to fetch tags');</span>
- return res.json() as Promise&lt;Category[]&gt;;
- }),
- ])
- .then(([feedsData, tagsData]) =&gt; {
- setFeeds(feedsData);
- setTags(tagsData);
- setLoading(false);
- })
- .catch((err) =&gt; {
- setError(err.message);
- setLoading(false);
- });
- }, []);
-&nbsp;
- if (loading) return &lt;div className="feed-list-loading"&gt;Loading feeds...&lt;/div&gt;;
- if (error) return &lt;div className="feed-list-error"&gt;Error: {error}&lt;/div&gt;;
-&nbsp;
- const handleLogout = () =&gt; {
- apiFetch('/api/logout', { method: 'POST' }).then(() =&gt; (window.location.href = '/v2/login'));
- };
-&nbsp;
- return (
- &lt;div className={`feed-list variant-${sidebarVariant}`}&gt;
- &lt;h1 className="logo" onClick={<span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >s</span>etSidebarVisible(false)}&gt;</span>
- 🐱
- &lt;/h1&gt;
-&nbsp;
- &lt;div className="search-section"&gt;
- &lt;form onSubmit={handleSearch} className="search-form"&gt;
- &lt;input
- type="search"
- placeholder="search..."
- value={searchQuery}
- onChange={(e) =&gt; setSearchQuery(e.target.value)}
- className="search-input"
- /&gt;
- &lt;/form&gt;
- &lt;/div&gt;
-&nbsp;
- &lt;div className="filter-section"&gt;
- &lt;ul className="filter-list"&gt;
- &lt;li className="unread_filter"&gt;
- &lt;Link to="/?filter=unread" className={currentFilter === 'unread' ? 'active' : <span class="branch-1 cbranch-no" title="branch not covered" >''} o</span>nClick={handleLinkClick}&gt;
- unread
- &lt;/Link&gt;
- &lt;/li&gt;
- &lt;li className="all_filter"&gt;
- &lt;Link to="/?filter=all" className={currentFilter === 'all' ? <span class="branch-0 cbranch-no" title="branch not covered" >'active' : '</span>'} onClick={handleLinkClick}&gt;
- all
- &lt;/Link&gt;
- &lt;/li&gt;
- &lt;li className="starred_filter"&gt;
- &lt;Link to="/?filter=starred" className={currentFilter === 'starred' ? <span class="branch-0 cbranch-no" title="branch not covered" >'active' : '</span>'} onClick={handleLinkClick}&gt;
- starred
- &lt;/Link&gt;
- &lt;/li&gt;
- &lt;/ul&gt;
- &lt;/div&gt;
-&nbsp;
- &lt;div className="tag-section"&gt;
- &lt;h4 onClick={toggleTags} className="section-header"&gt;
- &lt;span className={`caret ${tagsExpanded ? 'expanded' : <span class="branch-1 cbranch-no" title="branch not covered" >''}</span>`}&gt;▶&lt;/span&gt; Tags
- &lt;/h4&gt;
- {tagsExpanded &amp;&amp; (
- &lt;ul className="tag-list-items"&gt;
- {tags.map((tag) =&gt; (
- &lt;li key={tag.title} className="tag-item"&gt;
- &lt;Link
- to={`/tag/${encodeURIComponent(tag.title)}`}
- className={`tag-link ${tagName === tag.title ? <span class="branch-0 cbranch-no" title="branch not covered" >'active' : '</span>'}`}
- onClick={handleLinkClick}
- &gt;
- {tag.title}
- &lt;/Link&gt;
- &lt;/li&gt;
- ))}
- &lt;/ul&gt;
- )}
- &lt;/div&gt;
-&nbsp;
- &lt;div className="feed-section"&gt;
- &lt;h4 onClick={toggleFeeds} className="section-header"&gt;
- &lt;span className={`caret ${feedsExpanded ? 'expanded' : ''}`}&gt;▶&lt;/span&gt; Feeds
- &lt;/h4&gt;
- {feedsExpanded &amp;&amp;
- (feeds.length === 0 ? (
- &lt;p&gt;No feeds found.&lt;/p&gt;
- ) : (
- &lt;ul className="feed-list-items"&gt;
- {feeds.map((feed) =&gt; (
- &lt;li key={feed._id} className="sidebar-feed-item"&gt;
- &lt;Link
- to={`/feed/${feed._id}`}
- className={`feed-title ${feedId === String(feed._id) ? <span class="branch-0 cbranch-no" title="branch not covered" >'active' : '</span>'}`}
- onClick={handleLinkClick}
- &gt;
- {feed.title || <span class="branch-1 cbranch-no" title="branch not covered" >feed.url}</span>
- &lt;/Link&gt;
- &lt;/li&gt;
- ))}
- &lt;/ul&gt;
- ))}
- &lt;/div&gt;
-&nbsp;
- &lt;div className="nav-section"&gt;
- &lt;ul className="nav-list"&gt;
- &lt;li&gt;
- &lt;Link to="/settings" className="nav-link" onClick={handleLinkClick}&gt;
- settings
- &lt;/Link&gt;
- &lt;/li&gt;
- &lt;li&gt;
- &lt;button onClick={handleLogout} className="logout-link"&gt;
- logout
- &lt;/button&gt;
- &lt;/li&gt;
- &lt;/ul&gt;
- &lt;/div&gt;
-&nbsp;
- &lt;div className="theme-section"&gt;
- &lt;div className="theme-selector"&gt;
- &lt;button
- onClick={<span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >s</span>etTheme('light')}</span>
- className={theme === 'light' ? 'active' : <span class="branch-1 cbranch-no" title="branch not covered" >''}</span>
- title="Light Theme"
- &gt;
- ☀️
- &lt;/button&gt;
- &lt;button
- onClick={<span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >s</span>etTheme('dark')}</span>
- className={theme === 'dark' ? <span class="branch-0 cbranch-no" title="branch not covered" >'active' : '</span>'}
- title="Dark Theme"
- &gt;
- 🌙
- &lt;/button&gt;
- &lt;/div&gt;
- &lt;/div&gt;
- &lt;/div&gt;
- );
-}
-&nbsp;</pre></td></tr></table></pre>
-
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../../sorter.js"></script>
- <script src="../../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/components/Login.css.html b/frontend/coverage/src/components/Login.css.html
deleted file mode 100644
index 140a86b..0000000
--- a/frontend/coverage/src/components/Login.css.html
+++ /dev/null
@@ -1,274 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/components/Login.css</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../../prettify.css" />
- <link rel="stylesheet" href="../../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> Login.css</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line low'></div>
- <pre><table class="coverage">
-<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
-<a name='L2'></a><a href='#L2'>2</a>
-<a name='L3'></a><a href='#L3'>3</a>
-<a name='L4'></a><a href='#L4'>4</a>
-<a name='L5'></a><a href='#L5'>5</a>
-<a name='L6'></a><a href='#L6'>6</a>
-<a name='L7'></a><a href='#L7'>7</a>
-<a name='L8'></a><a href='#L8'>8</a>
-<a name='L9'></a><a href='#L9'>9</a>
-<a name='L10'></a><a href='#L10'>10</a>
-<a name='L11'></a><a href='#L11'>11</a>
-<a name='L12'></a><a href='#L12'>12</a>
-<a name='L13'></a><a href='#L13'>13</a>
-<a name='L14'></a><a href='#L14'>14</a>
-<a name='L15'></a><a href='#L15'>15</a>
-<a name='L16'></a><a href='#L16'>16</a>
-<a name='L17'></a><a href='#L17'>17</a>
-<a name='L18'></a><a href='#L18'>18</a>
-<a name='L19'></a><a href='#L19'>19</a>
-<a name='L20'></a><a href='#L20'>20</a>
-<a name='L21'></a><a href='#L21'>21</a>
-<a name='L22'></a><a href='#L22'>22</a>
-<a name='L23'></a><a href='#L23'>23</a>
-<a name='L24'></a><a href='#L24'>24</a>
-<a name='L25'></a><a href='#L25'>25</a>
-<a name='L26'></a><a href='#L26'>26</a>
-<a name='L27'></a><a href='#L27'>27</a>
-<a name='L28'></a><a href='#L28'>28</a>
-<a name='L29'></a><a href='#L29'>29</a>
-<a name='L30'></a><a href='#L30'>30</a>
-<a name='L31'></a><a href='#L31'>31</a>
-<a name='L32'></a><a href='#L32'>32</a>
-<a name='L33'></a><a href='#L33'>33</a>
-<a name='L34'></a><a href='#L34'>34</a>
-<a name='L35'></a><a href='#L35'>35</a>
-<a name='L36'></a><a href='#L36'>36</a>
-<a name='L37'></a><a href='#L37'>37</a>
-<a name='L38'></a><a href='#L38'>38</a>
-<a name='L39'></a><a href='#L39'>39</a>
-<a name='L40'></a><a href='#L40'>40</a>
-<a name='L41'></a><a href='#L41'>41</a>
-<a name='L42'></a><a href='#L42'>42</a>
-<a name='L43'></a><a href='#L43'>43</a>
-<a name='L44'></a><a href='#L44'>44</a>
-<a name='L45'></a><a href='#L45'>45</a>
-<a name='L46'></a><a href='#L46'>46</a>
-<a name='L47'></a><a href='#L47'>47</a>
-<a name='L48'></a><a href='#L48'>48</a>
-<a name='L49'></a><a href='#L49'>49</a>
-<a name='L50'></a><a href='#L50'>50</a>
-<a name='L51'></a><a href='#L51'>51</a>
-<a name='L52'></a><a href='#L52'>52</a>
-<a name='L53'></a><a href='#L53'>53</a>
-<a name='L54'></a><a href='#L54'>54</a>
-<a name='L55'></a><a href='#L55'>55</a>
-<a name='L56'></a><a href='#L56'>56</a>
-<a name='L57'></a><a href='#L57'>57</a>
-<a name='L58'></a><a href='#L58'>58</a>
-<a name='L59'></a><a href='#L59'>59</a>
-<a name='L60'></a><a href='#L60'>60</a>
-<a name='L61'></a><a href='#L61'>61</a>
-<a name='L62'></a><a href='#L62'>62</a>
-<a name='L63'></a><a href='#L63'>63</a>
-<a name='L64'></a><a href='#L64'>64</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">.login-container {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100vh;
- background-color: #f5f5f5;
-}
-&nbsp;
-.login-form {
- background: white;
- padding: 2rem;
- border-radius: 8px;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
- width: 100%;
- max-width: 400px;
-}
-&nbsp;
-.login-form h1 {
- margin-bottom: 2rem;
- text-align: center;
- color: #333;
-}
-&nbsp;
-.form-group {
- margin-bottom: 1.5rem;
-}
-&nbsp;
-.form-group label {
- display: block;
- margin-bottom: 0.5rem;
- font-weight: bold;
- color: #555;
-}
-&nbsp;
-.form-group input {
- width: 100%;
- padding: 0.75rem;
- border: 1px solid #ddd;
- border-radius: 4px;
- font-size: 1rem;
-}
-&nbsp;
-.error-message {
- color: #dc3545;
- margin-bottom: 1rem;
- text-align: center;
-}
-&nbsp;
-button[type='submit'] {
- width: 100%;
- padding: 0.75rem;
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 4px;
- font-size: 1rem;
- cursor: pointer;
- transition: background-color 0.2s;
-}
-&nbsp;
-button[type='submit']:hover {
- background-color: #0056b3;
-}
-&nbsp;</pre></td></tr></table></pre>
-
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../../sorter.js"></script>
- <script src="../../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/components/Login.tsx.html b/frontend/coverage/src/components/Login.tsx.html
deleted file mode 100644
index 111dcba..0000000
--- a/frontend/coverage/src/components/Login.tsx.html
+++ /dev/null
@@ -1,286 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/components/Login.tsx</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../../prettify.css" />
- <link rel="stylesheet" href="../../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> Login.tsx</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">100% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>20/20</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">83.33% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>5/6</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">100% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>4/4</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">100% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>20/20</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line high'></div>
- <pre><table class="coverage">
-<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
-<a name='L2'></a><a href='#L2'>2</a>
-<a name='L3'></a><a href='#L3'>3</a>
-<a name='L4'></a><a href='#L4'>4</a>
-<a name='L5'></a><a href='#L5'>5</a>
-<a name='L6'></a><a href='#L6'>6</a>
-<a name='L7'></a><a href='#L7'>7</a>
-<a name='L8'></a><a href='#L8'>8</a>
-<a name='L9'></a><a href='#L9'>9</a>
-<a name='L10'></a><a href='#L10'>10</a>
-<a name='L11'></a><a href='#L11'>11</a>
-<a name='L12'></a><a href='#L12'>12</a>
-<a name='L13'></a><a href='#L13'>13</a>
-<a name='L14'></a><a href='#L14'>14</a>
-<a name='L15'></a><a href='#L15'>15</a>
-<a name='L16'></a><a href='#L16'>16</a>
-<a name='L17'></a><a href='#L17'>17</a>
-<a name='L18'></a><a href='#L18'>18</a>
-<a name='L19'></a><a href='#L19'>19</a>
-<a name='L20'></a><a href='#L20'>20</a>
-<a name='L21'></a><a href='#L21'>21</a>
-<a name='L22'></a><a href='#L22'>22</a>
-<a name='L23'></a><a href='#L23'>23</a>
-<a name='L24'></a><a href='#L24'>24</a>
-<a name='L25'></a><a href='#L25'>25</a>
-<a name='L26'></a><a href='#L26'>26</a>
-<a name='L27'></a><a href='#L27'>27</a>
-<a name='L28'></a><a href='#L28'>28</a>
-<a name='L29'></a><a href='#L29'>29</a>
-<a name='L30'></a><a href='#L30'>30</a>
-<a name='L31'></a><a href='#L31'>31</a>
-<a name='L32'></a><a href='#L32'>32</a>
-<a name='L33'></a><a href='#L33'>33</a>
-<a name='L34'></a><a href='#L34'>34</a>
-<a name='L35'></a><a href='#L35'>35</a>
-<a name='L36'></a><a href='#L36'>36</a>
-<a name='L37'></a><a href='#L37'>37</a>
-<a name='L38'></a><a href='#L38'>38</a>
-<a name='L39'></a><a href='#L39'>39</a>
-<a name='L40'></a><a href='#L40'>40</a>
-<a name='L41'></a><a href='#L41'>41</a>
-<a name='L42'></a><a href='#L42'>42</a>
-<a name='L43'></a><a href='#L43'>43</a>
-<a name='L44'></a><a href='#L44'>44</a>
-<a name='L45'></a><a href='#L45'>45</a>
-<a name='L46'></a><a href='#L46'>46</a>
-<a name='L47'></a><a href='#L47'>47</a>
-<a name='L48'></a><a href='#L48'>48</a>
-<a name='L49'></a><a href='#L49'>49</a>
-<a name='L50'></a><a href='#L50'>50</a>
-<a name='L51'></a><a href='#L51'>51</a>
-<a name='L52'></a><a href='#L52'>52</a>
-<a name='L53'></a><a href='#L53'>53</a>
-<a name='L54'></a><a href='#L54'>54</a>
-<a name='L55'></a><a href='#L55'>55</a>
-<a name='L56'></a><a href='#L56'>56</a>
-<a name='L57'></a><a href='#L57'>57</a>
-<a name='L58'></a><a href='#L58'>58</a>
-<a name='L59'></a><a href='#L59'>59</a>
-<a name='L60'></a><a href='#L60'>60</a>
-<a name='L61'></a><a href='#L61'>61</a>
-<a name='L62'></a><a href='#L62'>62</a>
-<a name='L63'></a><a href='#L63'>63</a>
-<a name='L64'></a><a href='#L64'>64</a>
-<a name='L65'></a><a href='#L65'>65</a>
-<a name='L66'></a><a href='#L66'>66</a>
-<a name='L67'></a><a href='#L67'>67</a>
-<a name='L68'></a><a href='#L68'>68</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">17x</span>
-<span class="cline-any cline-yes">17x</span>
-<span class="cline-any cline-yes">17x</span>
-<span class="cline-any cline-yes">17x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">17x</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">17x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { useState, type FormEvent } from 'react';
-import { useNavigate } from 'react-router-dom';
-import './Login.css';
-&nbsp;
-import { apiFetch } from '../utils';
-&nbsp;
-export default function Login() {
- const [username, setUsername] = useState('neko');
- const [password, setPassword] = useState('');
- const [error, setError] = useState('');
- const navigate = useNavigate();
-&nbsp;
- const handleSubmit = async (e: FormEvent) =&gt; {
- e.preventDefault();
- setError('');
-&nbsp;
- try {
- // Use URLSearchParams to send as form-urlencoded, matching backend expectation
- const params = new URLSearchParams();
- params.append('username', username);
- params.append('password', password);
-&nbsp;
- const res = await apiFetch('/api/login', {
- method: 'POST',
- body: params,
- });
-&nbsp;
- if (res.ok) {
- navigate('/');
- } else {
- const data = await res.json();
- setError(data.message || <span class="branch-1 cbranch-no" title="branch not covered" >'Login failed')</span>;
- }
- } catch (_err) {
- setError('Network error');
- }
- };
-&nbsp;
- return (
- &lt;div className="login-container"&gt;
- &lt;form onSubmit={handleSubmit} className="login-form"&gt;
- &lt;h1&gt;neko rss mode&lt;/h1&gt;
- &lt;div className="form-group"&gt;
- &lt;label htmlFor="username"&gt;username&lt;/label&gt;
- &lt;input
- id="username"
- type="text"
- value={username}
- onChange={(e) =&gt; setUsername(e.target.value)}
- /&gt;
- &lt;/div&gt;
- &lt;div className="form-group"&gt;
- &lt;label htmlFor="password"&gt;password&lt;/label&gt;
- &lt;input
- id="password"
- type="password"
- value={password}
- onChange={(e) =&gt; setPassword(e.target.value)}
- autoFocus
- /&gt;
- &lt;/div&gt;
- {error &amp;&amp; &lt;div className="error-message"&gt;{error}&lt;/div&gt;}
- &lt;button type="submit"&gt;login&lt;/button&gt;
- &lt;/form&gt;
- &lt;/div&gt;
- );
-}
-&nbsp;</pre></td></tr></table></pre>
-
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../../sorter.js"></script>
- <script src="../../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/components/Settings.css.html b/frontend/coverage/src/components/Settings.css.html
deleted file mode 100644
index 4109bba..0000000
--- a/frontend/coverage/src/components/Settings.css.html
+++ /dev/null
@@ -1,802 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/components/Settings.css</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../../prettify.css" />
- <link rel="stylesheet" href="../../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> Settings.css</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">0% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>0/0</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line low'></div>
- <pre><table class="coverage">
-<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
-<a name='L2'></a><a href='#L2'>2</a>
-<a name='L3'></a><a href='#L3'>3</a>
-<a name='L4'></a><a href='#L4'>4</a>
-<a name='L5'></a><a href='#L5'>5</a>
-<a name='L6'></a><a href='#L6'>6</a>
-<a name='L7'></a><a href='#L7'>7</a>
-<a name='L8'></a><a href='#L8'>8</a>
-<a name='L9'></a><a href='#L9'>9</a>
-<a name='L10'></a><a href='#L10'>10</a>
-<a name='L11'></a><a href='#L11'>11</a>
-<a name='L12'></a><a href='#L12'>12</a>
-<a name='L13'></a><a href='#L13'>13</a>
-<a name='L14'></a><a href='#L14'>14</a>
-<a name='L15'></a><a href='#L15'>15</a>
-<a name='L16'></a><a href='#L16'>16</a>
-<a name='L17'></a><a href='#L17'>17</a>
-<a name='L18'></a><a href='#L18'>18</a>
-<a name='L19'></a><a href='#L19'>19</a>
-<a name='L20'></a><a href='#L20'>20</a>
-<a name='L21'></a><a href='#L21'>21</a>
-<a name='L22'></a><a href='#L22'>22</a>
-<a name='L23'></a><a href='#L23'>23</a>
-<a name='L24'></a><a href='#L24'>24</a>
-<a name='L25'></a><a href='#L25'>25</a>
-<a name='L26'></a><a href='#L26'>26</a>
-<a name='L27'></a><a href='#L27'>27</a>
-<a name='L28'></a><a href='#L28'>28</a>
-<a name='L29'></a><a href='#L29'>29</a>
-<a name='L30'></a><a href='#L30'>30</a>
-<a name='L31'></a><a href='#L31'>31</a>
-<a name='L32'></a><a href='#L32'>32</a>
-<a name='L33'></a><a href='#L33'>33</a>
-<a name='L34'></a><a href='#L34'>34</a>
-<a name='L35'></a><a href='#L35'>35</a>
-<a name='L36'></a><a href='#L36'>36</a>
-<a name='L37'></a><a href='#L37'>37</a>
-<a name='L38'></a><a href='#L38'>38</a>
-<a name='L39'></a><a href='#L39'>39</a>
-<a name='L40'></a><a href='#L40'>40</a>
-<a name='L41'></a><a href='#L41'>41</a>
-<a name='L42'></a><a href='#L42'>42</a>
-<a name='L43'></a><a href='#L43'>43</a>
-<a name='L44'></a><a href='#L44'>44</a>
-<a name='L45'></a><a href='#L45'>45</a>
-<a name='L46'></a><a href='#L46'>46</a>
-<a name='L47'></a><a href='#L47'>47</a>
-<a name='L48'></a><a href='#L48'>48</a>
-<a name='L49'></a><a href='#L49'>49</a>
-<a name='L50'></a><a href='#L50'>50</a>
-<a name='L51'></a><a href='#L51'>51</a>
-<a name='L52'></a><a href='#L52'>52</a>
-<a name='L53'></a><a href='#L53'>53</a>
-<a name='L54'></a><a href='#L54'>54</a>
-<a name='L55'></a><a href='#L55'>55</a>
-<a name='L56'></a><a href='#L56'>56</a>
-<a name='L57'></a><a href='#L57'>57</a>
-<a name='L58'></a><a href='#L58'>58</a>
-<a name='L59'></a><a href='#L59'>59</a>
-<a name='L60'></a><a href='#L60'>60</a>
-<a name='L61'></a><a href='#L61'>61</a>
-<a name='L62'></a><a href='#L62'>62</a>
-<a name='L63'></a><a href='#L63'>63</a>
-<a name='L64'></a><a href='#L64'>64</a>
-<a name='L65'></a><a href='#L65'>65</a>
-<a name='L66'></a><a href='#L66'>66</a>
-<a name='L67'></a><a href='#L67'>67</a>
-<a name='L68'></a><a href='#L68'>68</a>
-<a name='L69'></a><a href='#L69'>69</a>
-<a name='L70'></a><a href='#L70'>70</a>
-<a name='L71'></a><a href='#L71'>71</a>
-<a name='L72'></a><a href='#L72'>72</a>
-<a name='L73'></a><a href='#L73'>73</a>
-<a name='L74'></a><a href='#L74'>74</a>
-<a name='L75'></a><a href='#L75'>75</a>
-<a name='L76'></a><a href='#L76'>76</a>
-<a name='L77'></a><a href='#L77'>77</a>
-<a name='L78'></a><a href='#L78'>78</a>
-<a name='L79'></a><a href='#L79'>79</a>
-<a name='L80'></a><a href='#L80'>80</a>
-<a name='L81'></a><a href='#L81'>81</a>
-<a name='L82'></a><a href='#L82'>82</a>
-<a name='L83'></a><a href='#L83'>83</a>
-<a name='L84'></a><a href='#L84'>84</a>
-<a name='L85'></a><a href='#L85'>85</a>
-<a name='L86'></a><a href='#L86'>86</a>
-<a name='L87'></a><a href='#L87'>87</a>
-<a name='L88'></a><a href='#L88'>88</a>
-<a name='L89'></a><a href='#L89'>89</a>
-<a name='L90'></a><a href='#L90'>90</a>
-<a name='L91'></a><a href='#L91'>91</a>
-<a name='L92'></a><a href='#L92'>92</a>
-<a name='L93'></a><a href='#L93'>93</a>
-<a name='L94'></a><a href='#L94'>94</a>
-<a name='L95'></a><a href='#L95'>95</a>
-<a name='L96'></a><a href='#L96'>96</a>
-<a name='L97'></a><a href='#L97'>97</a>
-<a name='L98'></a><a href='#L98'>98</a>
-<a name='L99'></a><a href='#L99'>99</a>
-<a name='L100'></a><a href='#L100'>100</a>
-<a name='L101'></a><a href='#L101'>101</a>
-<a name='L102'></a><a href='#L102'>102</a>
-<a name='L103'></a><a href='#L103'>103</a>
-<a name='L104'></a><a href='#L104'>104</a>
-<a name='L105'></a><a href='#L105'>105</a>
-<a name='L106'></a><a href='#L106'>106</a>
-<a name='L107'></a><a href='#L107'>107</a>
-<a name='L108'></a><a href='#L108'>108</a>
-<a name='L109'></a><a href='#L109'>109</a>
-<a name='L110'></a><a href='#L110'>110</a>
-<a name='L111'></a><a href='#L111'>111</a>
-<a name='L112'></a><a href='#L112'>112</a>
-<a name='L113'></a><a href='#L113'>113</a>
-<a name='L114'></a><a href='#L114'>114</a>
-<a name='L115'></a><a href='#L115'>115</a>
-<a name='L116'></a><a href='#L116'>116</a>
-<a name='L117'></a><a href='#L117'>117</a>
-<a name='L118'></a><a href='#L118'>118</a>
-<a name='L119'></a><a href='#L119'>119</a>
-<a name='L120'></a><a href='#L120'>120</a>
-<a name='L121'></a><a href='#L121'>121</a>
-<a name='L122'></a><a href='#L122'>122</a>
-<a name='L123'></a><a href='#L123'>123</a>
-<a name='L124'></a><a href='#L124'>124</a>
-<a name='L125'></a><a href='#L125'>125</a>
-<a name='L126'></a><a href='#L126'>126</a>
-<a name='L127'></a><a href='#L127'>127</a>
-<a name='L128'></a><a href='#L128'>128</a>
-<a name='L129'></a><a href='#L129'>129</a>
-<a name='L130'></a><a href='#L130'>130</a>
-<a name='L131'></a><a href='#L131'>131</a>
-<a name='L132'></a><a href='#L132'>132</a>
-<a name='L133'></a><a href='#L133'>133</a>
-<a name='L134'></a><a href='#L134'>134</a>
-<a name='L135'></a><a href='#L135'>135</a>
-<a name='L136'></a><a href='#L136'>136</a>
-<a name='L137'></a><a href='#L137'>137</a>
-<a name='L138'></a><a href='#L138'>138</a>
-<a name='L139'></a><a href='#L139'>139</a>
-<a name='L140'></a><a href='#L140'>140</a>
-<a name='L141'></a><a href='#L141'>141</a>
-<a name='L142'></a><a href='#L142'>142</a>
-<a name='L143'></a><a href='#L143'>143</a>
-<a name='L144'></a><a href='#L144'>144</a>
-<a name='L145'></a><a href='#L145'>145</a>
-<a name='L146'></a><a href='#L146'>146</a>
-<a name='L147'></a><a href='#L147'>147</a>
-<a name='L148'></a><a href='#L148'>148</a>
-<a name='L149'></a><a href='#L149'>149</a>
-<a name='L150'></a><a href='#L150'>150</a>
-<a name='L151'></a><a href='#L151'>151</a>
-<a name='L152'></a><a href='#L152'>152</a>
-<a name='L153'></a><a href='#L153'>153</a>
-<a name='L154'></a><a href='#L154'>154</a>
-<a name='L155'></a><a href='#L155'>155</a>
-<a name='L156'></a><a href='#L156'>156</a>
-<a name='L157'></a><a href='#L157'>157</a>
-<a name='L158'></a><a href='#L158'>158</a>
-<a name='L159'></a><a href='#L159'>159</a>
-<a name='L160'></a><a href='#L160'>160</a>
-<a name='L161'></a><a href='#L161'>161</a>
-<a name='L162'></a><a href='#L162'>162</a>
-<a name='L163'></a><a href='#L163'>163</a>
-<a name='L164'></a><a href='#L164'>164</a>
-<a name='L165'></a><a href='#L165'>165</a>
-<a name='L166'></a><a href='#L166'>166</a>
-<a name='L167'></a><a href='#L167'>167</a>
-<a name='L168'></a><a href='#L168'>168</a>
-<a name='L169'></a><a href='#L169'>169</a>
-<a name='L170'></a><a href='#L170'>170</a>
-<a name='L171'></a><a href='#L171'>171</a>
-<a name='L172'></a><a href='#L172'>172</a>
-<a name='L173'></a><a href='#L173'>173</a>
-<a name='L174'></a><a href='#L174'>174</a>
-<a name='L175'></a><a href='#L175'>175</a>
-<a name='L176'></a><a href='#L176'>176</a>
-<a name='L177'></a><a href='#L177'>177</a>
-<a name='L178'></a><a href='#L178'>178</a>
-<a name='L179'></a><a href='#L179'>179</a>
-<a name='L180'></a><a href='#L180'>180</a>
-<a name='L181'></a><a href='#L181'>181</a>
-<a name='L182'></a><a href='#L182'>182</a>
-<a name='L183'></a><a href='#L183'>183</a>
-<a name='L184'></a><a href='#L184'>184</a>
-<a name='L185'></a><a href='#L185'>185</a>
-<a name='L186'></a><a href='#L186'>186</a>
-<a name='L187'></a><a href='#L187'>187</a>
-<a name='L188'></a><a href='#L188'>188</a>
-<a name='L189'></a><a href='#L189'>189</a>
-<a name='L190'></a><a href='#L190'>190</a>
-<a name='L191'></a><a href='#L191'>191</a>
-<a name='L192'></a><a href='#L192'>192</a>
-<a name='L193'></a><a href='#L193'>193</a>
-<a name='L194'></a><a href='#L194'>194</a>
-<a name='L195'></a><a href='#L195'>195</a>
-<a name='L196'></a><a href='#L196'>196</a>
-<a name='L197'></a><a href='#L197'>197</a>
-<a name='L198'></a><a href='#L198'>198</a>
-<a name='L199'></a><a href='#L199'>199</a>
-<a name='L200'></a><a href='#L200'>200</a>
-<a name='L201'></a><a href='#L201'>201</a>
-<a name='L202'></a><a href='#L202'>202</a>
-<a name='L203'></a><a href='#L203'>203</a>
-<a name='L204'></a><a href='#L204'>204</a>
-<a name='L205'></a><a href='#L205'>205</a>
-<a name='L206'></a><a href='#L206'>206</a>
-<a name='L207'></a><a href='#L207'>207</a>
-<a name='L208'></a><a href='#L208'>208</a>
-<a name='L209'></a><a href='#L209'>209</a>
-<a name='L210'></a><a href='#L210'>210</a>
-<a name='L211'></a><a href='#L211'>211</a>
-<a name='L212'></a><a href='#L212'>212</a>
-<a name='L213'></a><a href='#L213'>213</a>
-<a name='L214'></a><a href='#L214'>214</a>
-<a name='L215'></a><a href='#L215'>215</a>
-<a name='L216'></a><a href='#L216'>216</a>
-<a name='L217'></a><a href='#L217'>217</a>
-<a name='L218'></a><a href='#L218'>218</a>
-<a name='L219'></a><a href='#L219'>219</a>
-<a name='L220'></a><a href='#L220'>220</a>
-<a name='L221'></a><a href='#L221'>221</a>
-<a name='L222'></a><a href='#L222'>222</a>
-<a name='L223'></a><a href='#L223'>223</a>
-<a name='L224'></a><a href='#L224'>224</a>
-<a name='L225'></a><a href='#L225'>225</a>
-<a name='L226'></a><a href='#L226'>226</a>
-<a name='L227'></a><a href='#L227'>227</a>
-<a name='L228'></a><a href='#L228'>228</a>
-<a name='L229'></a><a href='#L229'>229</a>
-<a name='L230'></a><a href='#L230'>230</a>
-<a name='L231'></a><a href='#L231'>231</a>
-<a name='L232'></a><a href='#L232'>232</a>
-<a name='L233'></a><a href='#L233'>233</a>
-<a name='L234'></a><a href='#L234'>234</a>
-<a name='L235'></a><a href='#L235'>235</a>
-<a name='L236'></a><a href='#L236'>236</a>
-<a name='L237'></a><a href='#L237'>237</a>
-<a name='L238'></a><a href='#L238'>238</a>
-<a name='L239'></a><a href='#L239'>239</a>
-<a name='L240'></a><a href='#L240'>240</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">.settings-page.variant-glass {
- padding: 2.5rem;
- max-width: 800px;
- margin: 0 auto;
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
- border-radius: 24px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- font-family: system-ui, -apple-system, sans-serif;
- color: var(--text-color);
- margin-top: 2rem;
- margin-bottom: 2rem;
-}
-&nbsp;
-.settings-page.variant-glass h2,
-.settings-page.variant-glass h3 {
- font-weight: 700;
- letter-spacing: -0.02em;
- color: var(--text-color);
- opacity: 0.9;
-}
-&nbsp;
-.add-feed-section,
-.appearance-section,
-.import-section,
-.export-section,
-.feed-list-section {
- background: rgba(255, 255, 255, 0.03);
- padding: 1.5rem;
- border-radius: 16px;
- margin-bottom: 2rem;
- border: 1px solid rgba(255, 255, 255, 0.05);
- transition: all 0.3s ease;
-}
-&nbsp;
-.add-feed-section:hover,
-.appearance-section:hover,
-.import-section:hover,
-.export-section:hover,
-.feed-list-section:hover {
- background: rgba(255, 255, 255, 0.06);
- border-color: rgba(255, 255, 255, 0.1);
-}
-&nbsp;
-.font-selector {
- display: flex;
- align-items: center;
- gap: 1rem;
-}
-&nbsp;
-.font-select {
- padding: 0.6rem 1rem;
- border: 1px solid rgba(255, 255, 255, 0.1);
- background: rgba(0, 0, 0, 0.1);
- color: var(--text-color);
- border-radius: 20px;
- font-size: 1rem;
- min-width: 200px;
- cursor: pointer;
- outline: none;
- transition: border-color 0.2s;
-}
-&nbsp;
-.font-select:focus {
- border-color: rgba(255, 255, 255, 0.3);
-}
-&nbsp;
-.add-feed-form {
- display: flex;
- gap: 1rem;
-}
-&nbsp;
-.feed-input {
- flex: 1;
- padding: 0.6rem 1.2rem;
- border: 1px solid rgba(255, 255, 255, 0.1);
- background: rgba(0, 0, 0, 0.1);
- color: var(--text-color);
- border-radius: 20px;
- font-size: 1rem;
- outline: none;
- transition: border-color 0.2s;
-}
-&nbsp;
-.feed-input:focus {
- border-color: rgba(255, 255, 255, 0.3);
-}
-&nbsp;
-.error-message {
- color: #ff5252;
- margin-top: 1rem;
- font-weight: 600;
-}
-&nbsp;
-.settings-feed-list {
- list-style: none;
- padding: 0;
- border: 1px solid rgba(255, 255, 255, 0.05);
- border-radius: 12px;
- overflow: hidden;
-}
-&nbsp;
-.settings-feed-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 1.2rem;
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
- transition: background 0.2s;
-}
-&nbsp;
-.settings-feed-item:hover {
- background: rgba(255, 255, 255, 0.02);
-}
-&nbsp;
-.settings-feed-item:last-child {
- border-bottom: none;
-}
-&nbsp;
-.feed-info {
- display: flex;
- flex-direction: column;
- gap: 0.2rem;
-}
-&nbsp;
-.feed-title {
- font-weight: 600;
- font-size: 1.05rem;
- opacity: 0.9;
-}
-&nbsp;
-.feed-url {
- color: var(--text-color);
- opacity: 0.5;
- font-size: 0.85rem;
-}
-&nbsp;
-.delete-btn {
- background: rgba(255, 82, 82, 0.15);
- color: #ff8a80;
- border: 1px solid rgba(255, 82, 82, 0.2);
- padding: 0.5rem 1rem;
- border-radius: 12px;
- cursor: pointer;
- font-weight: 600;
- transition: all 0.2s;
-}
-&nbsp;
-.delete-btn:hover:not(:disabled) {
- background: rgba(255, 82, 82, 0.3);
- color: #fff;
- border-color: rgba(255, 82, 82, 0.4);
- transform: scale(1.05);
-}
-&nbsp;
-.import-export-section {
- display: flex;
- gap: 2rem;
-}
-&nbsp;
-@media (max-width: 600px) {
- .settings-page.variant-glass {
- padding: 1.5rem;
- margin-top: 1rem;
- }
-&nbsp;
- .add-feed-form {
- flex-direction: column;
- }
-&nbsp;
- .import-export-section {
- flex-direction: column;
- gap: 1rem;
- }
-&nbsp;
- .settings-feed-item {
- flex-direction: column;
- align-items: flex-start;
- gap: 1rem;
- }
-}
-&nbsp;
-.import-form {
- display: flex;
- flex-direction: column;
- gap: 1.2rem;
-}
-&nbsp;
-.file-input {
- font-size: 0.9rem;
- max-width: 100%;
- color: var(--text-color);
- opacity: 0.8;
-}
-&nbsp;
-.export-buttons {
- display: flex;
- gap: 0.8rem;
- flex-wrap: wrap;
-}
-&nbsp;
-.export-btn {
- display: inline-block;
- padding: 0.6rem 1.2rem;
- background: rgba(255, 255, 255, 0.05);
- color: var(--text-color);
- text-decoration: none;
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 12px;
- font-weight: 600;
- transition: all 0.2s;
-}
-&nbsp;
-.export-btn:hover {
- background: rgba(255, 255, 255, 0.1);
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
-}
-&nbsp;
-button:not(.delete-btn) {
- cursor: pointer;
- padding: 0.6rem 1.2rem;
- border-radius: 12px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- background: rgba(255, 255, 255, 0.1);
- color: var(--text-color);
- font-weight: 600;
- transition: all 0.2s;
-}
-&nbsp;
-button:not(.delete-btn):hover:not(:disabled) {
- background: rgba(255, 255, 255, 0.2);
- transform: scale(1.02);
-}
-&nbsp;
-button:disabled {
- opacity: 0.4;
- cursor: not-allowed;
-}</pre></td></tr></table></pre>
-
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../../sorter.js"></script>
- <script src="../../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/components/Settings.tsx.html b/frontend/coverage/src/components/Settings.tsx.html
deleted file mode 100644
index 892218e..0000000
--- a/frontend/coverage/src/components/Settings.tsx.html
+++ /dev/null
@@ -1,793 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/components/Settings.tsx</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../../prettify.css" />
- <link rel="stylesheet" href="../../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> Settings.tsx</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">80% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>60/75</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">66.66% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>20/30</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">85.18% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>23/27</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">87.87% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>58/66</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line high'></div>
- <pre><table class="coverage">
-<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
-<a name='L2'></a><a href='#L2'>2</a>
-<a name='L3'></a><a href='#L3'>3</a>
-<a name='L4'></a><a href='#L4'>4</a>
-<a name='L5'></a><a href='#L5'>5</a>
-<a name='L6'></a><a href='#L6'>6</a>
-<a name='L7'></a><a href='#L7'>7</a>
-<a name='L8'></a><a href='#L8'>8</a>
-<a name='L9'></a><a href='#L9'>9</a>
-<a name='L10'></a><a href='#L10'>10</a>
-<a name='L11'></a><a href='#L11'>11</a>
-<a name='L12'></a><a href='#L12'>12</a>
-<a name='L13'></a><a href='#L13'>13</a>
-<a name='L14'></a><a href='#L14'>14</a>
-<a name='L15'></a><a href='#L15'>15</a>
-<a name='L16'></a><a href='#L16'>16</a>
-<a name='L17'></a><a href='#L17'>17</a>
-<a name='L18'></a><a href='#L18'>18</a>
-<a name='L19'></a><a href='#L19'>19</a>
-<a name='L20'></a><a href='#L20'>20</a>
-<a name='L21'></a><a href='#L21'>21</a>
-<a name='L22'></a><a href='#L22'>22</a>
-<a name='L23'></a><a href='#L23'>23</a>
-<a name='L24'></a><a href='#L24'>24</a>
-<a name='L25'></a><a href='#L25'>25</a>
-<a name='L26'></a><a href='#L26'>26</a>
-<a name='L27'></a><a href='#L27'>27</a>
-<a name='L28'></a><a href='#L28'>28</a>
-<a name='L29'></a><a href='#L29'>29</a>
-<a name='L30'></a><a href='#L30'>30</a>
-<a name='L31'></a><a href='#L31'>31</a>
-<a name='L32'></a><a href='#L32'>32</a>
-<a name='L33'></a><a href='#L33'>33</a>
-<a name='L34'></a><a href='#L34'>34</a>
-<a name='L35'></a><a href='#L35'>35</a>
-<a name='L36'></a><a href='#L36'>36</a>
-<a name='L37'></a><a href='#L37'>37</a>
-<a name='L38'></a><a href='#L38'>38</a>
-<a name='L39'></a><a href='#L39'>39</a>
-<a name='L40'></a><a href='#L40'>40</a>
-<a name='L41'></a><a href='#L41'>41</a>
-<a name='L42'></a><a href='#L42'>42</a>
-<a name='L43'></a><a href='#L43'>43</a>
-<a name='L44'></a><a href='#L44'>44</a>
-<a name='L45'></a><a href='#L45'>45</a>
-<a name='L46'></a><a href='#L46'>46</a>
-<a name='L47'></a><a href='#L47'>47</a>
-<a name='L48'></a><a href='#L48'>48</a>
-<a name='L49'></a><a href='#L49'>49</a>
-<a name='L50'></a><a href='#L50'>50</a>
-<a name='L51'></a><a href='#L51'>51</a>
-<a name='L52'></a><a href='#L52'>52</a>
-<a name='L53'></a><a href='#L53'>53</a>
-<a name='L54'></a><a href='#L54'>54</a>
-<a name='L55'></a><a href='#L55'>55</a>
-<a name='L56'></a><a href='#L56'>56</a>
-<a name='L57'></a><a href='#L57'>57</a>
-<a name='L58'></a><a href='#L58'>58</a>
-<a name='L59'></a><a href='#L59'>59</a>
-<a name='L60'></a><a href='#L60'>60</a>
-<a name='L61'></a><a href='#L61'>61</a>
-<a name='L62'></a><a href='#L62'>62</a>
-<a name='L63'></a><a href='#L63'>63</a>
-<a name='L64'></a><a href='#L64'>64</a>
-<a name='L65'></a><a href='#L65'>65</a>
-<a name='L66'></a><a href='#L66'>66</a>
-<a name='L67'></a><a href='#L67'>67</a>
-<a name='L68'></a><a href='#L68'>68</a>
-<a name='L69'></a><a href='#L69'>69</a>
-<a name='L70'></a><a href='#L70'>70</a>
-<a name='L71'></a><a href='#L71'>71</a>
-<a name='L72'></a><a href='#L72'>72</a>
-<a name='L73'></a><a href='#L73'>73</a>
-<a name='L74'></a><a href='#L74'>74</a>
-<a name='L75'></a><a href='#L75'>75</a>
-<a name='L76'></a><a href='#L76'>76</a>
-<a name='L77'></a><a href='#L77'>77</a>
-<a name='L78'></a><a href='#L78'>78</a>
-<a name='L79'></a><a href='#L79'>79</a>
-<a name='L80'></a><a href='#L80'>80</a>
-<a name='L81'></a><a href='#L81'>81</a>
-<a name='L82'></a><a href='#L82'>82</a>
-<a name='L83'></a><a href='#L83'>83</a>
-<a name='L84'></a><a href='#L84'>84</a>
-<a name='L85'></a><a href='#L85'>85</a>
-<a name='L86'></a><a href='#L86'>86</a>
-<a name='L87'></a><a href='#L87'>87</a>
-<a name='L88'></a><a href='#L88'>88</a>
-<a name='L89'></a><a href='#L89'>89</a>
-<a name='L90'></a><a href='#L90'>90</a>
-<a name='L91'></a><a href='#L91'>91</a>
-<a name='L92'></a><a href='#L92'>92</a>
-<a name='L93'></a><a href='#L93'>93</a>
-<a name='L94'></a><a href='#L94'>94</a>
-<a name='L95'></a><a href='#L95'>95</a>
-<a name='L96'></a><a href='#L96'>96</a>
-<a name='L97'></a><a href='#L97'>97</a>
-<a name='L98'></a><a href='#L98'>98</a>
-<a name='L99'></a><a href='#L99'>99</a>
-<a name='L100'></a><a href='#L100'>100</a>
-<a name='L101'></a><a href='#L101'>101</a>
-<a name='L102'></a><a href='#L102'>102</a>
-<a name='L103'></a><a href='#L103'>103</a>
-<a name='L104'></a><a href='#L104'>104</a>
-<a name='L105'></a><a href='#L105'>105</a>
-<a name='L106'></a><a href='#L106'>106</a>
-<a name='L107'></a><a href='#L107'>107</a>
-<a name='L108'></a><a href='#L108'>108</a>
-<a name='L109'></a><a href='#L109'>109</a>
-<a name='L110'></a><a href='#L110'>110</a>
-<a name='L111'></a><a href='#L111'>111</a>
-<a name='L112'></a><a href='#L112'>112</a>
-<a name='L113'></a><a href='#L113'>113</a>
-<a name='L114'></a><a href='#L114'>114</a>
-<a name='L115'></a><a href='#L115'>115</a>
-<a name='L116'></a><a href='#L116'>116</a>
-<a name='L117'></a><a href='#L117'>117</a>
-<a name='L118'></a><a href='#L118'>118</a>
-<a name='L119'></a><a href='#L119'>119</a>
-<a name='L120'></a><a href='#L120'>120</a>
-<a name='L121'></a><a href='#L121'>121</a>
-<a name='L122'></a><a href='#L122'>122</a>
-<a name='L123'></a><a href='#L123'>123</a>
-<a name='L124'></a><a href='#L124'>124</a>
-<a name='L125'></a><a href='#L125'>125</a>
-<a name='L126'></a><a href='#L126'>126</a>
-<a name='L127'></a><a href='#L127'>127</a>
-<a name='L128'></a><a href='#L128'>128</a>
-<a name='L129'></a><a href='#L129'>129</a>
-<a name='L130'></a><a href='#L130'>130</a>
-<a name='L131'></a><a href='#L131'>131</a>
-<a name='L132'></a><a href='#L132'>132</a>
-<a name='L133'></a><a href='#L133'>133</a>
-<a name='L134'></a><a href='#L134'>134</a>
-<a name='L135'></a><a href='#L135'>135</a>
-<a name='L136'></a><a href='#L136'>136</a>
-<a name='L137'></a><a href='#L137'>137</a>
-<a name='L138'></a><a href='#L138'>138</a>
-<a name='L139'></a><a href='#L139'>139</a>
-<a name='L140'></a><a href='#L140'>140</a>
-<a name='L141'></a><a href='#L141'>141</a>
-<a name='L142'></a><a href='#L142'>142</a>
-<a name='L143'></a><a href='#L143'>143</a>
-<a name='L144'></a><a href='#L144'>144</a>
-<a name='L145'></a><a href='#L145'>145</a>
-<a name='L146'></a><a href='#L146'>146</a>
-<a name='L147'></a><a href='#L147'>147</a>
-<a name='L148'></a><a href='#L148'>148</a>
-<a name='L149'></a><a href='#L149'>149</a>
-<a name='L150'></a><a href='#L150'>150</a>
-<a name='L151'></a><a href='#L151'>151</a>
-<a name='L152'></a><a href='#L152'>152</a>
-<a name='L153'></a><a href='#L153'>153</a>
-<a name='L154'></a><a href='#L154'>154</a>
-<a name='L155'></a><a href='#L155'>155</a>
-<a name='L156'></a><a href='#L156'>156</a>
-<a name='L157'></a><a href='#L157'>157</a>
-<a name='L158'></a><a href='#L158'>158</a>
-<a name='L159'></a><a href='#L159'>159</a>
-<a name='L160'></a><a href='#L160'>160</a>
-<a name='L161'></a><a href='#L161'>161</a>
-<a name='L162'></a><a href='#L162'>162</a>
-<a name='L163'></a><a href='#L163'>163</a>
-<a name='L164'></a><a href='#L164'>164</a>
-<a name='L165'></a><a href='#L165'>165</a>
-<a name='L166'></a><a href='#L166'>166</a>
-<a name='L167'></a><a href='#L167'>167</a>
-<a name='L168'></a><a href='#L168'>168</a>
-<a name='L169'></a><a href='#L169'>169</a>
-<a name='L170'></a><a href='#L170'>170</a>
-<a name='L171'></a><a href='#L171'>171</a>
-<a name='L172'></a><a href='#L172'>172</a>
-<a name='L173'></a><a href='#L173'>173</a>
-<a name='L174'></a><a href='#L174'>174</a>
-<a name='L175'></a><a href='#L175'>175</a>
-<a name='L176'></a><a href='#L176'>176</a>
-<a name='L177'></a><a href='#L177'>177</a>
-<a name='L178'></a><a href='#L178'>178</a>
-<a name='L179'></a><a href='#L179'>179</a>
-<a name='L180'></a><a href='#L180'>180</a>
-<a name='L181'></a><a href='#L181'>181</a>
-<a name='L182'></a><a href='#L182'>182</a>
-<a name='L183'></a><a href='#L183'>183</a>
-<a name='L184'></a><a href='#L184'>184</a>
-<a name='L185'></a><a href='#L185'>185</a>
-<a name='L186'></a><a href='#L186'>186</a>
-<a name='L187'></a><a href='#L187'>187</a>
-<a name='L188'></a><a href='#L188'>188</a>
-<a name='L189'></a><a href='#L189'>189</a>
-<a name='L190'></a><a href='#L190'>190</a>
-<a name='L191'></a><a href='#L191'>191</a>
-<a name='L192'></a><a href='#L192'>192</a>
-<a name='L193'></a><a href='#L193'>193</a>
-<a name='L194'></a><a href='#L194'>194</a>
-<a name='L195'></a><a href='#L195'>195</a>
-<a name='L196'></a><a href='#L196'>196</a>
-<a name='L197'></a><a href='#L197'>197</a>
-<a name='L198'></a><a href='#L198'>198</a>
-<a name='L199'></a><a href='#L199'>199</a>
-<a name='L200'></a><a href='#L200'>200</a>
-<a name='L201'></a><a href='#L201'>201</a>
-<a name='L202'></a><a href='#L202'>202</a>
-<a name='L203'></a><a href='#L203'>203</a>
-<a name='L204'></a><a href='#L204'>204</a>
-<a name='L205'></a><a href='#L205'>205</a>
-<a name='L206'></a><a href='#L206'>206</a>
-<a name='L207'></a><a href='#L207'>207</a>
-<a name='L208'></a><a href='#L208'>208</a>
-<a name='L209'></a><a href='#L209'>209</a>
-<a name='L210'></a><a href='#L210'>210</a>
-<a name='L211'></a><a href='#L211'>211</a>
-<a name='L212'></a><a href='#L212'>212</a>
-<a name='L213'></a><a href='#L213'>213</a>
-<a name='L214'></a><a href='#L214'>214</a>
-<a name='L215'></a><a href='#L215'>215</a>
-<a name='L216'></a><a href='#L216'>216</a>
-<a name='L217'></a><a href='#L217'>217</a>
-<a name='L218'></a><a href='#L218'>218</a>
-<a name='L219'></a><a href='#L219'>219</a>
-<a name='L220'></a><a href='#L220'>220</a>
-<a name='L221'></a><a href='#L221'>221</a>
-<a name='L222'></a><a href='#L222'>222</a>
-<a name='L223'></a><a href='#L223'>223</a>
-<a name='L224'></a><a href='#L224'>224</a>
-<a name='L225'></a><a href='#L225'>225</a>
-<a name='L226'></a><a href='#L226'>226</a>
-<a name='L227'></a><a href='#L227'>227</a>
-<a name='L228'></a><a href='#L228'>228</a>
-<a name='L229'></a><a href='#L229'>229</a>
-<a name='L230'></a><a href='#L230'>230</a>
-<a name='L231'></a><a href='#L231'>231</a>
-<a name='L232'></a><a href='#L232'>232</a>
-<a name='L233'></a><a href='#L233'>233</a>
-<a name='L234'></a><a href='#L234'>234</a>
-<a name='L235'></a><a href='#L235'>235</a>
-<a name='L236'></a><a href='#L236'>236</a>
-<a name='L237'></a><a href='#L237'>237</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">34x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">34x</span>
-<span class="cline-any cline-yes">34x</span>
-<span class="cline-any cline-yes">34x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">34x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">34x</span>
-<span class="cline-any cline-yes">9x</span>
-<span class="cline-any cline-yes">9x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">9x</span>
-<span class="cline-any cline-yes">9x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">9x</span>
-<span class="cline-any cline-yes">9x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">34x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">7x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">34x</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">34x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">34x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">34x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">34x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">6x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import React, { useEffect, useState } from 'react';
-import type { Feed } from '../types';
-import './Settings.css';
-import { apiFetch } from '../utils';
-&nbsp;
-interface SettingsProps {
- fontTheme?: string;
- setFontTheme?: (t: string) =&gt; void;
-}
-&nbsp;
-export default function Settings({ fontTheme, setFontTheme }: SettingsProps) {
- const [feeds, setFeeds] = useState&lt;Feed[]&gt;([]);
- /* ... existing state ... */
- const [newFeedUrl, setNewFeedUrl] = useState('');
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState&lt;string | null&gt;(null);
-&nbsp;
- const [importFile, setImportFile] = useState&lt;File | null&gt;(null);
-&nbsp;
- /* ... existing fetchFeeds ... */
- const fetchFeeds = React.useCallback(() =&gt; {
- setLoading(true);
- apiFetch('/api/feed/')
- .then((res) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to fetch feeds');</span>
- return res.json();
- })
- .then((data) =&gt; {
- setFeeds(data);
- setLoading(false);
- })
- .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; {
-<span class="cstat-no" title="statement not covered" > setError(err.message);</span>
-<span class="cstat-no" title="statement not covered" > setLoading(false);</span>
- });
- }, []);
-&nbsp;
- useEffect(() =&gt; {
- // eslint-disable-next-line
- fetchFeeds();
- }, [fetchFeeds]);
-&nbsp;
- /* ... existing handlers ... */
- const handleAddFeed = (e: React.FormEvent) =&gt; {
- e.preventDefault();
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!newFeedUrl) <span class="cstat-no" title="statement not covered" >return;</span>
-&nbsp;
- setLoading(true);
- apiFetch('/api/feed/', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ url: newFeedUrl }),
- })
- .then((res) =&gt; {
- if (!res.ok) throw new Error('Failed to add feed');
- return res.json();
- })
- .then(() =&gt; {
- setNewFeedUrl('');
- fetchFeeds();
- })
- .catch((err) =&gt; {
- setError(err.message);
- setLoading(false);
- });
- };
-&nbsp;
- const handleDeleteFeed = (id: number) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!globalThis.confirm('Are you sure you want to delete this feed?')) <span class="cstat-no" title="statement not covered" >return;</span>
-&nbsp;
- setLoading(true);
- apiFetch(`/api/feed/${id}`, {
- method: 'DELETE',
- })
- .then((res) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to delete feed');</span>
- setFeeds(feeds.filter((f) =&gt; f._id !== id));
- setLoading(false);
- })
- .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; {
-<span class="cstat-no" title="statement not covered" > setError(err.message);</span>
-<span class="cstat-no" title="statement not covered" > setLoading(false);</span>
- });
- };
-&nbsp;
- const handleImport = (e: React.FormEvent) =&gt; {
- e.preventDefault();
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!importFile) <span class="cstat-no" title="statement not covered" >return;</span>
-&nbsp;
- setLoading(true);
- const formData = new FormData();
- formData.append('file', importFile);
- formData.append('format', 'opml');
-&nbsp;
- apiFetch('/api/import', {
- method: 'POST',
- body: formData,
- })
- .then((res) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to import feeds');</span>
- return res.json();
- })
- .then(() =&gt; {
- setImportFile(null);
- fetchFeeds();
- alert('Import successful!');
- })
- .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; {
-<span class="cstat-no" title="statement not covered" > setError(err.message);</span>
-<span class="cstat-no" title="statement not covered" > setLoading(false);</span>
- });
- };
-&nbsp;
- const handleCrawl = () =&gt; {
- setLoading(true);
- apiFetch('/api/crawl', {
- method: 'POST',
- })
- .then((res) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to start crawl');</span>
- return res.json();
- })
- .then(() =&gt; {
- setLoading(false);
- alert('Crawl started!');
- })
- .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; {
-<span class="cstat-no" title="statement not covered" > setError(err.message);</span>
-<span class="cstat-no" title="statement not covered" > setLoading(false);</span>
- });
- };
-&nbsp;
- return (
- &lt;div className="settings-page variant-glass"&gt;
- &lt;h2&gt;Settings&lt;/h2&gt;
-&nbsp;
- {setFontTheme &amp;&amp; (
- &lt;div className="appearance-section"&gt;
- &lt;h3&gt;Appearance&lt;/h3&gt;
- &lt;div className="font-selector"&gt;
- &lt;label htmlFor="font-theme-select"&gt;Font Theme:&lt;/label&gt;
- &lt;select
- id="font-theme-select"
- value={fontTheme || <span class="branch-1 cbranch-no" title="branch not covered" >'default'}</span>
- onChange={(e) =&gt; setFontTheme(e.target.value)}
- className="font-select"
- &gt;
- &lt;option value="default"&gt;Default&lt;/option&gt;
- &lt;option value="serif"&gt;Serif&lt;/option&gt;
- &lt;option value="sans"&gt;Sans-Serif&lt;/option&gt;
- &lt;option value="mono"&gt;Monospace&lt;/option&gt;
- &lt;/select&gt;
- &lt;/div&gt;
- &lt;/div&gt;
- )}
-&nbsp;
- &lt;div className="add-feed-section"&gt;
- &lt;h3&gt;Add New Feed&lt;/h3&gt;
- &lt;form onSubmit={handleAddFeed} className="add-feed-form"&gt;
- &lt;input
- type="url"
- value={newFeedUrl}
- onChange={(e) =&gt; setNewFeedUrl(e.target.value)}
- placeholder="https://example.com/feed.xml"
- required
- className="feed-input"
- disabled={loading}
- /&gt;
- &lt;button type="submit" disabled={loading}&gt;
- Add Feed
- &lt;/button&gt;
- &lt;/form&gt;
- &lt;/div&gt;
-&nbsp;
- &lt;div className="import-export-section"&gt;
- &lt;div className="import-section"&gt;
- &lt;h3&gt;Import Feeds (OPML)&lt;/h3&gt;
- &lt;form onSubmit={handleImport} className="import-form"&gt;
- &lt;input
- type="file"
- accept=".opml,.xml,.txt"
- aria-label="Import Feeds"
- onChange={(e) =&gt; setImportFile(e.target.files?.[0] || <span class="branch-1 cbranch-no" title="branch not covered" >null)</span>}
- className="file-input"
- disabled={loading}
- /&gt;
- &lt;button type="submit" disabled={!importFile || loading}&gt;
- Import
- &lt;/button&gt;
- &lt;/form&gt;
- &lt;/div&gt;
-&nbsp;
- &lt;div className="export-section"&gt;
- &lt;h3&gt;Export Feeds&lt;/h3&gt;
- &lt;div className="export-buttons"&gt;
- &lt;a href="/api/export/opml" className="export-btn"&gt;OPML&lt;/a&gt;
- &lt;a href="/api/export/text" className="export-btn"&gt;Text&lt;/a&gt;
- &lt;a href="/api/export/json" className="export-btn"&gt;JSON&lt;/a&gt;
- &lt;/div&gt;
- &lt;/div&gt;
-&nbsp;
- &lt;div className="crawl-section"&gt;
- &lt;h3&gt;Actions&lt;/h3&gt;
- &lt;button onClick={handleCrawl} disabled={loading} className="crawl-btn"&gt;
- Crawl All Feeds Now
- &lt;/button&gt;
- &lt;/div&gt;
- &lt;/div&gt;
-&nbsp;
- {error &amp;&amp; &lt;p className="error-message"&gt;{error}&lt;/p&gt;}
-&nbsp;
- &lt;div className="feed-list-section"&gt;
- &lt;h3&gt;Manage Feeds&lt;/h3&gt;
- {loading &amp;&amp; &lt;p&gt;Loading...&lt;/p&gt;}
- &lt;ul className="settings-feed-list"&gt;
- {feeds.map((feed) =&gt; (
- &lt;li key={feed._id} className="settings-feed-item"&gt;
- &lt;div className="feed-info"&gt;
- &lt;span className="feed-title"&gt;{feed.title || <span class="branch-1 cbranch-no" title="branch not covered" >'(No Title)'}&lt;</span>/span&gt;
- &lt;span className="feed-url"&gt;{feed.url}&lt;/span&gt;
- &lt;/div&gt;
- &lt;button
- onClick={() =&gt; handleDeleteFeed(feed._id)}
- className="delete-btn"
- disabled={loading}
- title="Delete Feed"
- &gt;
- Delete
- &lt;/button&gt;
- &lt;/li&gt;
- ))}
- &lt;/ul&gt;
- &lt;/div&gt;
- &lt;/div&gt;
- );
-}
-&nbsp;</pre></td></tr></table></pre>
-
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../../sorter.js"></script>
- <script src="../../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/components/index.html b/frontend/coverage/src/components/index.html
deleted file mode 100644
index fc333bb..0000000
--- a/frontend/coverage/src/components/index.html
+++ /dev/null
@@ -1,266 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src/components</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../../prettify.css" />
- <link rel="stylesheet" href="../../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../../index.html">All files</a> src/components</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">86.21% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>269/312</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">74.61% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>144/193</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">84.94% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>79/93</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">88.81% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>254/286</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line high'></div>
- <div class="pad1">
-<table class="coverage-summary">
-<thead>
-<tr>
- <th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
- <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
- <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
- <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
- <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
- <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
- <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
- <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
- <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
- <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
-</tr>
-</thead>
-<tbody><tr>
- <td class="file empty" data-value="FeedItem.css"><a href="FeedItem.css.html">FeedItem.css</a></td>
- <td data-value="0" class="pic empty">
- <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
- </td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- </tr>
-
-<tr>
- <td class="file medium" data-value="FeedItem.tsx"><a href="FeedItem.tsx.html">FeedItem.tsx</a></td>
- <td data-value="78.12" class="pic medium">
- <div class="chart"><div class="cover-fill" style="width: 78%"></div><div class="cover-empty" style="width: 22%"></div></div>
- </td>
- <td data-value="78.12" class="pct medium">78.12%</td>
- <td data-value="32" class="abs medium">25/32</td>
- <td data-value="86.95" class="pct high">86.95%</td>
- <td data-value="23" class="abs high">20/23</td>
- <td data-value="83.33" class="pct high">83.33%</td>
- <td data-value="12" class="abs high">10/12</td>
- <td data-value="80.64" class="pct high">80.64%</td>
- <td data-value="31" class="abs high">25/31</td>
- </tr>
-
-<tr>
- <td class="file empty" data-value="FeedItems.css"><a href="FeedItems.css.html">FeedItems.css</a></td>
- <td data-value="0" class="pic empty">
- <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
- </td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- </tr>
-
-<tr>
- <td class="file high" data-value="FeedItems.tsx"><a href="FeedItems.tsx.html">FeedItems.tsx</a></td>
- <td data-value="89.23" class="pic high">
- <div class="chart"><div class="cover-fill" style="width: 89%"></div><div class="cover-empty" style="width: 11%"></div></div>
- </td>
- <td data-value="89.23" class="pct high">89.23%</td>
- <td data-value="130" class="abs high">116/130</td>
- <td data-value="76.19" class="pct medium">76.19%</td>
- <td data-value="84" class="abs medium">64/84</td>
- <td data-value="87.09" class="pct high">87.09%</td>
- <td data-value="31" class="abs high">27/31</td>
- <td data-value="89.07" class="pct high">89.07%</td>
- <td data-value="119" class="abs high">106/119</td>
- </tr>
-
-<tr>
- <td class="file empty" data-value="FeedList.css"><a href="FeedList.css.html">FeedList.css</a></td>
- <td data-value="0" class="pic empty">
- <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
- </td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- </tr>
-
-<tr>
- <td class="file high" data-value="FeedList.tsx"><a href="FeedList.tsx.html">FeedList.tsx</a></td>
- <td data-value="87.27" class="pic high">
- <div class="chart"><div class="cover-fill" style="width: 87%"></div><div class="cover-empty" style="width: 13%"></div></div>
- </td>
- <td data-value="87.27" class="pct high">87.27%</td>
- <td data-value="55" class="abs high">48/55</td>
- <td data-value="70" class="pct medium">70%</td>
- <td data-value="50" class="abs medium">35/50</td>
- <td data-value="78.94" class="pct medium">78.94%</td>
- <td data-value="19" class="abs medium">15/19</td>
- <td data-value="90" class="pct high">90%</td>
- <td data-value="50" class="abs high">45/50</td>
- </tr>
-
-<tr>
- <td class="file empty" data-value="FeedListVariants.css"><a href="FeedListVariants.css.html">FeedListVariants.css</a></td>
- <td data-value="0" class="pic empty">
- <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
- </td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- </tr>
-
-<tr>
- <td class="file empty" data-value="Login.css"><a href="Login.css.html">Login.css</a></td>
- <td data-value="0" class="pic empty">
- <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
- </td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- </tr>
-
-<tr>
- <td class="file high" data-value="Login.tsx"><a href="Login.tsx.html">Login.tsx</a></td>
- <td data-value="100" class="pic high">
- <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
- </td>
- <td data-value="100" class="pct high">100%</td>
- <td data-value="20" class="abs high">20/20</td>
- <td data-value="83.33" class="pct high">83.33%</td>
- <td data-value="6" class="abs high">5/6</td>
- <td data-value="100" class="pct high">100%</td>
- <td data-value="4" class="abs high">4/4</td>
- <td data-value="100" class="pct high">100%</td>
- <td data-value="20" class="abs high">20/20</td>
- </tr>
-
-<tr>
- <td class="file empty" data-value="Settings.css"><a href="Settings.css.html">Settings.css</a></td>
- <td data-value="0" class="pic empty">
- <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
- </td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- </tr>
-
-<tr>
- <td class="file high" data-value="Settings.tsx"><a href="Settings.tsx.html">Settings.tsx</a></td>
- <td data-value="80" class="pic high">
- <div class="chart"><div class="cover-fill" style="width: 80%"></div><div class="cover-empty" style="width: 20%"></div></div>
- </td>
- <td data-value="80" class="pct high">80%</td>
- <td data-value="75" class="abs high">60/75</td>
- <td data-value="66.66" class="pct medium">66.66%</td>
- <td data-value="30" class="abs medium">20/30</td>
- <td data-value="85.18" class="pct high">85.18%</td>
- <td data-value="27" class="abs high">23/27</td>
- <td data-value="87.87" class="pct high">87.87%</td>
- <td data-value="66" class="abs high">58/66</td>
- </tr>
-
-</tbody>
-</table>
-</div>
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../../sorter.js"></script>
- <script src="../../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/coverage/src/index.html b/frontend/coverage/src/index.html
deleted file mode 100644
index 091ffa0..0000000
--- a/frontend/coverage/src/index.html
+++ /dev/null
@@ -1,146 +0,0 @@
-
-<!doctype html>
-<html lang="en">
-
-<head>
- <title>Code coverage report for src</title>
- <meta charset="utf-8" />
- <link rel="stylesheet" href="../prettify.css" />
- <link rel="stylesheet" href="../base.css" />
- <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type='text/css'>
- .coverage-summary .sorter {
- background-image: url(../sort-arrow-sprite.png);
- }
- </style>
-</head>
-
-<body>
-<div class='wrapper'>
- <div class='pad1'>
- <h1><a href="../index.html">All files</a> src</h1>
- <div class='clearfix'>
-
- <div class='fl pad1y space-right2'>
- <span class="strong">70.21% </span>
- <span class="quiet">Statements</span>
- <span class='fraction'>33/47</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">65.71% </span>
- <span class="quiet">Branches</span>
- <span class='fraction'>23/35</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">60% </span>
- <span class="quiet">Functions</span>
- <span class='fraction'>9/15</span>
- </div>
-
-
- <div class='fl pad1y space-right2'>
- <span class="strong">71.11% </span>
- <span class="quiet">Lines</span>
- <span class='fraction'>32/45</span>
- </div>
-
-
- </div>
- <p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
- </p>
- <template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch">
- </div>
- </template>
- </div>
- <div class='status-line medium'></div>
- <div class="pad1">
-<table class="coverage-summary">
-<thead>
-<tr>
- <th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
- <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
- <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
- <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
- <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
- <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
- <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
- <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
- <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
- <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
-</tr>
-</thead>
-<tbody><tr>
- <td class="file empty" data-value="App.css"><a href="App.css.html">App.css</a></td>
- <td data-value="0" class="pic empty">
- <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
- </td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- <td data-value="0" class="pct empty">0%</td>
- <td data-value="0" class="abs empty">0/0</td>
- </tr>
-
-<tr>
- <td class="file medium" data-value="App.tsx"><a href="App.tsx.html">App.tsx</a></td>
- <td data-value="65.71" class="pic medium">
- <div class="chart"><div class="cover-fill" style="width: 65%"></div><div class="cover-empty" style="width: 35%"></div></div>
- </td>
- <td data-value="65.71" class="pct medium">65.71%</td>
- <td data-value="35" class="abs medium">23/35</td>
- <td data-value="60" class="pct medium">60%</td>
- <td data-value="25" class="abs medium">15/25</td>
- <td data-value="53.84" class="pct medium">53.84%</td>
- <td data-value="13" class="abs medium">7/13</td>
- <td data-value="64.7" class="pct medium">64.7%</td>
- <td data-value="34" class="abs medium">22/34</td>
- </tr>
-
-<tr>
- <td class="file high" data-value="utils.ts"><a href="utils.ts.html">utils.ts</a></td>
- <td data-value="83.33" class="pic high">
- <div class="chart"><div class="cover-fill" style="width: 83%"></div><div class="cover-empty" style="width: 17%"></div></div>
- </td>
- <td data-value="83.33" class="pct high">83.33%</td>
- <td data-value="12" class="abs high">10/12</td>
- <td data-value="80" class="pct high">80%</td>
- <td data-value="10" class="abs high">8/10</td>
- <td data-value="100" class="pct high">100%</td>
- <td data-value="2" class="abs high">2/2</td>
- <td data-value="90.9" class="pct high">90.9%</td>
- <td data-value="11" class="abs high">10/11</td>
- </tr>
-
-</tbody>
-</table>
-</div>
- <div class='push'></div><!-- for sticky footer -->
- </div><!-- /wrapper -->
- <div class='footer quiet pad2 space-top1 center small'>
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-15T05:30:50.842Z
- </div>
- <script src="../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../sorter.js"></script>
- <script src="../block-navigation.js"></script>
- </body>
-</html>
- \ No newline at end of file
diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js
deleted file mode 100644
index d2de7f8..0000000
--- a/frontend/eslint.config.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import js from '@eslint/js';
-import globals from 'globals';
-import reactHooks from 'eslint-plugin-react-hooks';
-import reactRefresh from 'eslint-plugin-react-refresh';
-import tseslint from 'typescript-eslint';
-import eslintConfigPrettier from 'eslint-config-prettier';
-
-export default tseslint.config(
- { ignores: ['dist', 'coverage', 'playwright-report'] },
- {
- extends: [js.configs.recommended, ...tseslint.configs.recommended],
- files: ['**/*.{ts,tsx}'],
- languageOptions: {
- ecmaVersion: 2020,
- globals: globals.browser,
- },
- plugins: {
- 'react-hooks': reactHooks,
- 'react-refresh': reactRefresh,
- },
- rules: {
- ...reactHooks.configs.recommended.rules,
- 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
- '@typescript-eslint/no-unused-vars': ['warn', {
- argsIgnorePattern: '^_',
- caughtErrorsIgnorePattern: '^_'
- }],
- '@typescript-eslint/no-explicit-any': 'warn',
- },
- },
- eslintConfigPrettier
-);
diff --git a/frontend/index.html b/frontend/index.html
deleted file mode 100644
index a1475a5..0000000
--- a/frontend/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!doctype html>
-<html lang="en">
- <head>
- <meta charset="UTF-8" />
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, viewport-fit=cover" />
- <title>Neko Reader</title>
- </head>
- <body>
- <div id="root"></div>
- <script type="module" src="/src/main.tsx"></script>
- </body>
-</html>
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
deleted file mode 100644
index fda0116..0000000
--- a/frontend/package-lock.json
+++ /dev/null
@@ -1,4469 +0,0 @@
-{
- "name": "frontend",
- "version": "0.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "frontend",
- "version": "0.0.0",
- "dependencies": {
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
- "react-router-dom": "^7.13.0"
- },
- "devDependencies": {
- "@eslint/js": "^9.39.1",
- "@playwright/test": "^1.58.2",
- "@testing-library/jest-dom": "^6.9.1",
- "@testing-library/react": "^16.3.2",
- "@types/node": "^24.10.1",
- "@types/react": "^19.2.7",
- "@types/react-dom": "^19.2.3",
- "@vitejs/plugin-react": "^5.1.1",
- "@vitest/coverage-v8": "^4.0.18",
- "eslint": "^9.39.1",
- "eslint-config-prettier": "^10.1.8",
- "eslint-plugin-prettier": "^5.5.5",
- "eslint-plugin-react-hooks": "^7.0.1",
- "eslint-plugin-react-refresh": "^0.4.24",
- "globals": "^16.5.0",
- "jsdom": "^28.0.0",
- "prettier": "^3.8.1",
- "typescript": "~5.9.3",
- "typescript-eslint": "^8.48.0",
- "vite": "^7.3.1",
- "vitest": "^4.0.18"
- }
- },
- "node_modules/@acemir/cssom": {
- "version": "0.9.31",
- "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz",
- "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==",
- "dev": true
- },
- "node_modules/@adobe/css-tools": {
- "version": "4.4.4",
- "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
- "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==",
- "dev": true
- },
- "node_modules/@asamuzakjp/css-color": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz",
- "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==",
- "dev": true,
- "dependencies": {
- "@csstools/css-calc": "^3.0.0",
- "@csstools/css-color-parser": "^4.0.1",
- "@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0",
- "lru-cache": "^11.2.5"
- }
- },
- "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
- "version": "11.2.6",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
- "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
- "dev": true,
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/@asamuzakjp/dom-selector": {
- "version": "6.7.8",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.8.tgz",
- "integrity": "sha512-stisC1nULNc9oH5lakAj8MH88ZxeGxzyWNDfbdCxvJSJIvDsHNZqYvscGTgy/ysgXWLJPt6K/4t0/GjvtKcFJQ==",
- "dev": true,
- "dependencies": {
- "@asamuzakjp/nwsapi": "^2.3.9",
- "bidi-js": "^1.0.3",
- "css-tree": "^3.1.0",
- "is-potential-custom-element-name": "^1.0.1",
- "lru-cache": "^11.2.5"
- }
- },
- "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
- "version": "11.2.6",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
- "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
- "dev": true,
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/@asamuzakjp/nwsapi": {
- "version": "2.3.9",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
- "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
- "dev": true
- },
- "node_modules/@babel/code-frame": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
- "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
- "dev": true,
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.28.5",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
- "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
- "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
- "dev": true,
- "dependencies": {
- "@babel/code-frame": "^7.29.0",
- "@babel/generator": "^7.29.0",
- "@babel/helper-compilation-targets": "^7.28.6",
- "@babel/helper-module-transforms": "^7.28.6",
- "@babel/helpers": "^7.28.6",
- "@babel/parser": "^7.29.0",
- "@babel/template": "^7.28.6",
- "@babel/traverse": "^7.29.0",
- "@babel/types": "^7.29.0",
- "@jridgewell/remapping": "^2.3.5",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.29.1",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
- "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
- "dev": true,
- "dependencies": {
- "@babel/parser": "^7.29.0",
- "@babel/types": "^7.29.0",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
- "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
- "dev": true,
- "dependencies": {
- "@babel/compat-data": "^7.28.6",
- "@babel/helper-validator-option": "^7.27.1",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-globals": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
- "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
- "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
- "dev": true,
- "dependencies": {
- "@babel/traverse": "^7.28.6",
- "@babel/types": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
- "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
- "dev": true,
- "dependencies": {
- "@babel/helper-module-imports": "^7.28.6",
- "@babel/helper-validator-identifier": "^7.28.5",
- "@babel/traverse": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-plugin-utils": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
- "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
- "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
- "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
- "dev": true,
- "dependencies": {
- "@babel/template": "^7.28.6",
- "@babel/types": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
- "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
- "dev": true,
- "dependencies": {
- "@babel/types": "^7.29.0"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-self": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
- "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
- "dev": true,
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-source": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
- "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
- "dev": true,
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/runtime": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
- "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/template": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
- "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
- "dev": true,
- "dependencies": {
- "@babel/code-frame": "^7.28.6",
- "@babel/parser": "^7.28.6",
- "@babel/types": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
- "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
- "dev": true,
- "dependencies": {
- "@babel/code-frame": "^7.29.0",
- "@babel/generator": "^7.29.0",
- "@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.29.0",
- "@babel/template": "^7.28.6",
- "@babel/types": "^7.29.0",
- "debug": "^4.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
- "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
- "dev": true,
- "dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@bcoe/v8-coverage": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
- "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
- "dev": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@csstools/color-helpers": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.1.tgz",
- "integrity": "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "engines": {
- "node": ">=20.19.0"
- }
- },
- "node_modules/@csstools/css-calc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.0.tgz",
- "integrity": "sha512-JWouqB5za07FUA2iXZWq4gPXNGWXjRwlfwEXNr7cSsGr7OKgzhDVwkJjlsrbqSyFmDGSi1Rt7zs8ln87jX9yRg==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "engines": {
- "node": ">=20.19.0"
- },
- "peerDependencies": {
- "@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0"
- }
- },
- "node_modules/@csstools/css-color-parser": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.1.tgz",
- "integrity": "sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "dependencies": {
- "@csstools/color-helpers": "^6.0.1",
- "@csstools/css-calc": "^3.0.0"
- },
- "engines": {
- "node": ">=20.19.0"
- },
- "peerDependencies": {
- "@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0"
- }
- },
- "node_modules/@csstools/css-parser-algorithms": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
- "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "engines": {
- "node": ">=20.19.0"
- },
- "peerDependencies": {
- "@csstools/css-tokenizer": "^4.0.0"
- }
- },
- "node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.0.27",
- "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz",
- "integrity": "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ]
- },
- "node_modules/@csstools/css-tokenizer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
- "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "engines": {
- "node": ">=20.19.0"
- }
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
- "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
- "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
- "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
- "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
- "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
- "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
- "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
- "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
- "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
- "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
- "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
- "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
- "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
- "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
- "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
- "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
- "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
- "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
- "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
- "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
- "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openharmony-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
- "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
- "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
- "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
- "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
- "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
- "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
- "dev": true,
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
- }
- },
- "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
- "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
- "dev": true,
- "engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
- }
- },
- "node_modules/@eslint/config-array": {
- "version": "0.21.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
- "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
- "dev": true,
- "dependencies": {
- "@eslint/object-schema": "^2.1.7",
- "debug": "^4.3.1",
- "minimatch": "^3.1.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/config-helpers": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
- "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
- "dev": true,
- "dependencies": {
- "@eslint/core": "^0.17.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/core": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
- "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
- "dev": true,
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/eslintrc": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
- "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
- "dev": true,
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.1",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@eslint/js": {
- "version": "9.39.2",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
- "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
- "dev": true,
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- }
- },
- "node_modules/@eslint/object-schema": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
- "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
- "dev": true,
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/plugin-kit": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
- "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
- "dev": true,
- "dependencies": {
- "@eslint/core": "^0.17.0",
- "levn": "^0.4.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@exodus/bytes": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.14.1.tgz",
- "integrity": "sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==",
- "dev": true,
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- },
- "peerDependencies": {
- "@noble/hashes": "^1.8.0 || ^2.0.0"
- },
- "peerDependenciesMeta": {
- "@noble/hashes": {
- "optional": true
- }
- }
- },
- "node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
- "dev": true,
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node": {
- "version": "0.16.7",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
- "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
- "dev": true,
- "dependencies": {
- "@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.4.0"
- },
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
- "dev": true,
- "engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
- "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
- "dev": true,
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/remapping": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "dev": true,
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.31",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@pkgr/core": {
- "version": "0.2.9",
- "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
- "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
- "dev": true,
- "engines": {
- "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/pkgr"
- }
- },
- "node_modules/@playwright/test": {
- "version": "1.58.2",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
- "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
- "dev": true,
- "dependencies": {
- "playwright": "1.58.2"
- },
- "bin": {
- "playwright": "cli.js"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-rc.3",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
- "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
- "dev": true
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
- "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
- "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
- "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
- "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
- "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
- "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
- "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
- "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
- "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
- "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
- "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
- "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
- "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
- "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
- "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
- "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
- "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
- "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
- "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
- "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "openbsd"
- ]
- },
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
- "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "openharmony"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
- "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
- "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
- "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
- "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@standard-schema/spec": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
- "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
- "dev": true
- },
- "node_modules/@testing-library/dom": {
- "version": "10.4.1",
- "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
- "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
- "dev": true,
- "peer": true,
- "dependencies": {
- "@babel/code-frame": "^7.10.4",
- "@babel/runtime": "^7.12.5",
- "@types/aria-query": "^5.0.1",
- "aria-query": "5.3.0",
- "dom-accessibility-api": "^0.5.9",
- "lz-string": "^1.5.0",
- "picocolors": "1.1.1",
- "pretty-format": "^27.0.2"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@testing-library/jest-dom": {
- "version": "6.9.1",
- "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
- "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==",
- "dev": true,
- "dependencies": {
- "@adobe/css-tools": "^4.4.0",
- "aria-query": "^5.0.0",
- "css.escape": "^1.5.1",
- "dom-accessibility-api": "^0.6.3",
- "picocolors": "^1.1.1",
- "redent": "^3.0.0"
- },
- "engines": {
- "node": ">=14",
- "npm": ">=6",
- "yarn": ">=1"
- }
- },
- "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
- "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
- "dev": true
- },
- "node_modules/@testing-library/react": {
- "version": "16.3.2",
- "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
- "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
- "dev": true,
- "dependencies": {
- "@babel/runtime": "^7.12.5"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "@testing-library/dom": "^10.0.0",
- "@types/react": "^18.0.0 || ^19.0.0",
- "@types/react-dom": "^18.0.0 || ^19.0.0",
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@types/aria-query": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
- "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
- "dev": true,
- "peer": true
- },
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "dev": true,
- "dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
- },
- "node_modules/@types/babel__generator": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
- "dev": true,
- "dependencies": {
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
- "dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
- "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
- "dev": true,
- "dependencies": {
- "@babel/types": "^7.28.2"
- }
- },
- "node_modules/@types/chai": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
- "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
- "dev": true,
- "dependencies": {
- "@types/deep-eql": "*",
- "assertion-error": "^2.0.1"
- }
- },
- "node_modules/@types/deep-eql": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
- "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
- "dev": true
- },
- "node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "dev": true
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true
- },
- "node_modules/@types/node": {
- "version": "24.10.13",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz",
- "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==",
- "dev": true,
- "dependencies": {
- "undici-types": "~7.16.0"
- }
- },
- "node_modules/@types/react": {
- "version": "19.2.14",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
- "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
- "dev": true,
- "dependencies": {
- "csstype": "^3.2.2"
- }
- },
- "node_modules/@types/react-dom": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
- "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
- "dev": true,
- "peerDependencies": {
- "@types/react": "^19.2.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz",
- "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==",
- "dev": true,
- "dependencies": {
- "@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.55.0",
- "@typescript-eslint/type-utils": "8.55.0",
- "@typescript-eslint/utils": "8.55.0",
- "@typescript-eslint/visitor-keys": "8.55.0",
- "ignore": "^7.0.5",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.4.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^8.55.0",
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
- "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
- "dev": true,
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/@typescript-eslint/parser": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz",
- "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/scope-manager": "8.55.0",
- "@typescript-eslint/types": "8.55.0",
- "@typescript-eslint/typescript-estree": "8.55.0",
- "@typescript-eslint/visitor-keys": "8.55.0",
- "debug": "^4.4.3"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/project-service": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz",
- "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.55.0",
- "@typescript-eslint/types": "^8.55.0",
- "debug": "^4.4.3"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz",
- "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/types": "8.55.0",
- "@typescript-eslint/visitor-keys": "8.55.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz",
- "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==",
- "dev": true,
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/type-utils": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz",
- "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/types": "8.55.0",
- "@typescript-eslint/typescript-estree": "8.55.0",
- "@typescript-eslint/utils": "8.55.0",
- "debug": "^4.4.3",
- "ts-api-utils": "^2.4.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/types": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz",
- "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==",
- "dev": true,
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz",
- "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/project-service": "8.55.0",
- "@typescript-eslint/tsconfig-utils": "8.55.0",
- "@typescript-eslint/types": "8.55.0",
- "@typescript-eslint/visitor-keys": "8.55.0",
- "debug": "^4.4.3",
- "minimatch": "^9.0.5",
- "semver": "^7.7.3",
- "tinyglobby": "^0.2.15",
- "ts-api-utils": "^2.4.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
- "dev": true,
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@typescript-eslint/utils": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz",
- "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==",
- "dev": true,
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.55.0",
- "@typescript-eslint/types": "8.55.0",
- "@typescript-eslint/typescript-estree": "8.55.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz",
- "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/types": "8.55.0",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@vitejs/plugin-react": {
- "version": "5.1.4",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
- "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==",
- "dev": true,
- "dependencies": {
- "@babel/core": "^7.29.0",
- "@babel/plugin-transform-react-jsx-self": "^7.27.1",
- "@babel/plugin-transform-react-jsx-source": "^7.27.1",
- "@rolldown/pluginutils": "1.0.0-rc.3",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.18.0"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
- }
- },
- "node_modules/@vitest/coverage-v8": {
- "version": "4.0.18",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz",
- "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==",
- "dev": true,
- "dependencies": {
- "@bcoe/v8-coverage": "^1.0.2",
- "@vitest/utils": "4.0.18",
- "ast-v8-to-istanbul": "^0.3.10",
- "istanbul-lib-coverage": "^3.2.2",
- "istanbul-lib-report": "^3.0.1",
- "istanbul-reports": "^3.2.0",
- "magicast": "^0.5.1",
- "obug": "^2.1.1",
- "std-env": "^3.10.0",
- "tinyrainbow": "^3.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "@vitest/browser": "4.0.18",
- "vitest": "4.0.18"
- },
- "peerDependenciesMeta": {
- "@vitest/browser": {
- "optional": true
- }
- }
- },
- "node_modules/@vitest/expect": {
- "version": "4.0.18",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
- "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==",
- "dev": true,
- "dependencies": {
- "@standard-schema/spec": "^1.0.0",
- "@types/chai": "^5.2.2",
- "@vitest/spy": "4.0.18",
- "@vitest/utils": "4.0.18",
- "chai": "^6.2.1",
- "tinyrainbow": "^3.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/mocker": {
- "version": "4.0.18",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz",
- "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==",
- "dev": true,
- "dependencies": {
- "@vitest/spy": "4.0.18",
- "estree-walker": "^3.0.3",
- "magic-string": "^0.30.21"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "msw": "^2.4.9",
- "vite": "^6.0.0 || ^7.0.0-0"
- },
- "peerDependenciesMeta": {
- "msw": {
- "optional": true
- },
- "vite": {
- "optional": true
- }
- }
- },
- "node_modules/@vitest/pretty-format": {
- "version": "4.0.18",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz",
- "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==",
- "dev": true,
- "dependencies": {
- "tinyrainbow": "^3.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/runner": {
- "version": "4.0.18",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz",
- "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==",
- "dev": true,
- "dependencies": {
- "@vitest/utils": "4.0.18",
- "pathe": "^2.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/snapshot": {
- "version": "4.0.18",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz",
- "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==",
- "dev": true,
- "dependencies": {
- "@vitest/pretty-format": "4.0.18",
- "magic-string": "^0.30.21",
- "pathe": "^2.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/spy": {
- "version": "4.0.18",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz",
- "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==",
- "dev": true,
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/utils": {
- "version": "4.0.18",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz",
- "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==",
- "dev": true,
- "dependencies": {
- "@vitest/pretty-format": "4.0.18",
- "tinyrainbow": "^3.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
- "dev": true,
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/agent-base": {
- "version": "7.1.4",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
- "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
- "dev": true,
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "peer": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
- "node_modules/aria-query": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
- "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
- "dev": true,
- "dependencies": {
- "dequal": "^2.0.3"
- }
- },
- "node_modules/assertion-error": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
- "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
- "dev": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/ast-v8-to-istanbul": {
- "version": "0.3.11",
- "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz",
- "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==",
- "dev": true,
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.31",
- "estree-walker": "^3.0.3",
- "js-tokens": "^10.0.0"
- }
- },
- "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
- "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
- "dev": true
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
- },
- "node_modules/baseline-browser-mapping": {
- "version": "2.9.19",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
- "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
- "dev": true,
- "bin": {
- "baseline-browser-mapping": "dist/cli.js"
- }
- },
- "node_modules/bidi-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
- "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
- "dev": true,
- "dependencies": {
- "require-from-string": "^2.0.2"
- }
- },
- "node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/browserslist": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
- "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "baseline-browser-mapping": "^2.9.0",
- "caniuse-lite": "^1.0.30001759",
- "electron-to-chromium": "^1.5.263",
- "node-releases": "^2.0.27",
- "update-browserslist-db": "^1.2.0"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001769",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz",
- "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ]
- },
- "node_modules/chai": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
- "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
- "dev": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true
- },
- "node_modules/cookie": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
- "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/css-tree": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
- "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
- "dev": true,
- "dependencies": {
- "mdn-data": "2.12.2",
- "source-map-js": "^1.0.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
- }
- },
- "node_modules/css.escape": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
- "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
- "dev": true
- },
- "node_modules/cssstyle": {
- "version": "5.3.7",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz",
- "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==",
- "dev": true,
- "dependencies": {
- "@asamuzakjp/css-color": "^4.1.1",
- "@csstools/css-syntax-patches-for-csstree": "^1.0.21",
- "css-tree": "^3.1.0",
- "lru-cache": "^11.2.4"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/cssstyle/node_modules/lru-cache": {
- "version": "11.2.6",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
- "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
- "dev": true,
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/csstype": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true
- },
- "node_modules/data-urls": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
- "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
- "dev": true,
- "dependencies": {
- "whatwg-mimetype": "^5.0.0",
- "whatwg-url": "^16.0.0"
- },
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- }
- },
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/decimal.js": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
- "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
- "dev": true
- },
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true
- },
- "node_modules/dequal": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
- "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/dom-accessibility-api": {
- "version": "0.5.16",
- "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
- "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
- "dev": true,
- "peer": true
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.286",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
- "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
- "dev": true
- },
- "node_modules/entities": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
- "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
- "dev": true,
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/es-module-lexer": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
- "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
- "dev": true
- },
- "node_modules/esbuild": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
- "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
- "dev": true,
- "hasInstallScript": true,
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.27.3",
- "@esbuild/android-arm": "0.27.3",
- "@esbuild/android-arm64": "0.27.3",
- "@esbuild/android-x64": "0.27.3",
- "@esbuild/darwin-arm64": "0.27.3",
- "@esbuild/darwin-x64": "0.27.3",
- "@esbuild/freebsd-arm64": "0.27.3",
- "@esbuild/freebsd-x64": "0.27.3",
- "@esbuild/linux-arm": "0.27.3",
- "@esbuild/linux-arm64": "0.27.3",
- "@esbuild/linux-ia32": "0.27.3",
- "@esbuild/linux-loong64": "0.27.3",
- "@esbuild/linux-mips64el": "0.27.3",
- "@esbuild/linux-ppc64": "0.27.3",
- "@esbuild/linux-riscv64": "0.27.3",
- "@esbuild/linux-s390x": "0.27.3",
- "@esbuild/linux-x64": "0.27.3",
- "@esbuild/netbsd-arm64": "0.27.3",
- "@esbuild/netbsd-x64": "0.27.3",
- "@esbuild/openbsd-arm64": "0.27.3",
- "@esbuild/openbsd-x64": "0.27.3",
- "@esbuild/openharmony-arm64": "0.27.3",
- "@esbuild/sunos-x64": "0.27.3",
- "@esbuild/win32-arm64": "0.27.3",
- "@esbuild/win32-ia32": "0.27.3",
- "@esbuild/win32-x64": "0.27.3"
- }
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint": {
- "version": "9.39.2",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
- "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
- "dev": true,
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.8.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.1",
- "@eslint/config-helpers": "^0.4.2",
- "@eslint/core": "^0.17.0",
- "@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.39.2",
- "@eslint/plugin-kit": "^0.4.1",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.2",
- "@types/estree": "^1.0.6",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.4.0",
- "eslint-visitor-keys": "^4.2.1",
- "espree": "^10.4.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-config-prettier": {
- "version": "10.1.8",
- "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
- "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
- "dev": true,
- "bin": {
- "eslint-config-prettier": "bin/cli.js"
- },
- "funding": {
- "url": "https://opencollective.com/eslint-config-prettier"
- },
- "peerDependencies": {
- "eslint": ">=7.0.0"
- }
- },
- "node_modules/eslint-plugin-prettier": {
- "version": "5.5.5",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz",
- "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==",
- "dev": true,
- "dependencies": {
- "prettier-linter-helpers": "^1.0.1",
- "synckit": "^0.11.12"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint-plugin-prettier"
- },
- "peerDependencies": {
- "@types/eslint": ">=8.0.0",
- "eslint": ">=8.0.0",
- "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
- "prettier": ">=3.0.0"
- },
- "peerDependenciesMeta": {
- "@types/eslint": {
- "optional": true
- },
- "eslint-config-prettier": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-plugin-react-hooks": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
- "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
- "dev": true,
- "dependencies": {
- "@babel/core": "^7.24.4",
- "@babel/parser": "^7.24.4",
- "hermes-parser": "^0.25.1",
- "zod": "^3.25.0 || ^4.0.0",
- "zod-validation-error": "^3.5.0 || ^4.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
- }
- },
- "node_modules/eslint-plugin-react-refresh": {
- "version": "0.4.26",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
- "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
- "dev": true,
- "peerDependencies": {
- "eslint": ">=8.40"
- }
- },
- "node_modules/eslint-scope": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
- "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
- "dev": true,
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
- "dev": true,
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/espree": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
- "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
- "dev": true,
- "dependencies": {
- "acorn": "^8.15.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/esquery": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
- "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
- "dev": true,
- "dependencies": {
- "estraverse": "^5.1.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
- "dev": true,
- "dependencies": {
- "@types/estree": "^1.0.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/expect-type": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
- "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
- "dev": true,
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
- },
- "node_modules/fast-diff": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
- "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
- "dev": true
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true
- },
- "node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
- "dev": true,
- "dependencies": {
- "flat-cache": "^4.0.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
- "dev": true
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/globals": {
- "version": "16.5.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
- "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
- "dev": true,
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/hermes-estree": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
- "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
- "dev": true
- },
- "node_modules/hermes-parser": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
- "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
- "dev": true,
- "dependencies": {
- "hermes-estree": "0.25.1"
- }
- },
- "node_modules/html-encoding-sniffer": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
- "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
- "dev": true,
- "dependencies": {
- "@exodus/bytes": "^1.6.0"
- },
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- }
- },
- "node_modules/html-escaper": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
- "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
- "dev": true
- },
- "node_modules/http-proxy-agent": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
- "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
- "dev": true,
- "dependencies": {
- "agent-base": "^7.1.0",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/https-proxy-agent": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
- "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
- "dev": true,
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/indent-string": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
- "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-potential-custom-element-name": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
- "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
- "dev": true
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true
- },
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-report": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
- "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
- "dev": true,
- "dependencies": {
- "istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^4.0.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/istanbul-reports": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
- "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
- "dev": true,
- "dependencies": {
- "html-escaper": "^2.0.0",
- "istanbul-lib-report": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true
- },
- "node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
- "dev": true,
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/jsdom": {
- "version": "28.0.0",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.0.0.tgz",
- "integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==",
- "dev": true,
- "dependencies": {
- "@acemir/cssom": "^0.9.31",
- "@asamuzakjp/dom-selector": "^6.7.6",
- "@exodus/bytes": "^1.11.0",
- "cssstyle": "^5.3.7",
- "data-urls": "^7.0.0",
- "decimal.js": "^10.6.0",
- "html-encoding-sniffer": "^6.0.0",
- "http-proxy-agent": "^7.0.2",
- "https-proxy-agent": "^7.0.6",
- "is-potential-custom-element-name": "^1.0.1",
- "parse5": "^8.0.0",
- "saxes": "^6.0.0",
- "symbol-tree": "^3.2.4",
- "tough-cookie": "^6.0.0",
- "undici": "^7.20.0",
- "w3c-xmlserializer": "^5.0.0",
- "webidl-conversions": "^8.0.1",
- "whatwg-mimetype": "^5.0.0",
- "whatwg-url": "^16.0.0",
- "xml-name-validator": "^5.0.0"
- },
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- },
- "peerDependencies": {
- "canvas": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "canvas": {
- "optional": true
- }
- }
- },
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "dev": true,
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true
- },
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
- "dev": true
- },
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true,
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
- "dependencies": {
- "json-buffer": "3.0.1"
- }
- },
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true
- },
- "node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/lz-string": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
- "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
- "dev": true,
- "peer": true,
- "bin": {
- "lz-string": "bin/bin.js"
- }
- },
- "node_modules/magic-string": {
- "version": "0.30.21",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
- "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
- "dev": true,
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
- }
- },
- "node_modules/magicast": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz",
- "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==",
- "dev": true,
- "dependencies": {
- "@babel/parser": "^7.29.0",
- "@babel/types": "^7.29.0",
- "source-map-js": "^1.2.1"
- }
- },
- "node_modules/make-dir": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
- "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
- "dev": true,
- "dependencies": {
- "semver": "^7.5.3"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/make-dir/node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
- "dev": true,
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/mdn-data": {
- "version": "2.12.2",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
- "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
- "dev": true
- },
- "node_modules/min-indent": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
- "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true
- },
- "node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true
- },
- "node_modules/node-releases": {
- "version": "2.0.27",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
- "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
- "dev": true
- },
- "node_modules/obug": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
- "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
- "dev": true,
- "funding": [
- "https://github.com/sponsors/sxzz",
- "https://opencollective.com/debug"
- ]
- },
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
- "dev": true,
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/parse5": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
- "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
- "dev": true,
- "dependencies": {
- "entities": "^6.0.0"
- },
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pathe": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
- "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
- "dev": true
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true
- },
- "node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/playwright": {
- "version": "1.58.2",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
- "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
- "dev": true,
- "dependencies": {
- "playwright-core": "1.58.2"
- },
- "bin": {
- "playwright": "cli.js"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "fsevents": "2.3.2"
- }
- },
- "node_modules/playwright-core": {
- "version": "1.58.2",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
- "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
- "dev": true,
- "bin": {
- "playwright-core": "cli.js"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/playwright/node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/postcss": {
- "version": "8.5.6",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
- "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "nanoid": "^3.3.11",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true,
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/prettier": {
- "version": "3.8.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
- "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
- "dev": true,
- "bin": {
- "prettier": "bin/prettier.cjs"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/prettier/prettier?sponsor=1"
- }
- },
- "node_modules/prettier-linter-helpers": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz",
- "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==",
- "dev": true,
- "dependencies": {
- "fast-diff": "^1.1.2"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/pretty-format": {
- "version": "27.5.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
- "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
- "dev": true,
- "peer": true,
- "dependencies": {
- "ansi-regex": "^5.0.1",
- "ansi-styles": "^5.0.0",
- "react-is": "^17.0.1"
- },
- "engines": {
- "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
- }
- },
- "node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "peer": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/react": {
- "version": "19.2.4",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
- "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "19.2.4",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
- "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
- "dependencies": {
- "scheduler": "^0.27.0"
- },
- "peerDependencies": {
- "react": "^19.2.4"
- }
- },
- "node_modules/react-is": {
- "version": "17.0.2",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
- "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
- "dev": true,
- "peer": true
- },
- "node_modules/react-refresh": {
- "version": "0.18.0",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
- "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-router": {
- "version": "7.13.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
- "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
- "dependencies": {
- "cookie": "^1.0.1",
- "set-cookie-parser": "^2.6.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/react-router-dom": {
- "version": "7.13.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
- "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
- "dependencies": {
- "react-router": "7.13.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
- }
- },
- "node_modules/redent": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
- "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
- "dev": true,
- "dependencies": {
- "indent-string": "^4.0.0",
- "strip-indent": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/require-from-string": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/rollup": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
- "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
- "dev": true,
- "dependencies": {
- "@types/estree": "1.0.8"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.57.1",
- "@rollup/rollup-android-arm64": "4.57.1",
- "@rollup/rollup-darwin-arm64": "4.57.1",
- "@rollup/rollup-darwin-x64": "4.57.1",
- "@rollup/rollup-freebsd-arm64": "4.57.1",
- "@rollup/rollup-freebsd-x64": "4.57.1",
- "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
- "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
- "@rollup/rollup-linux-arm64-gnu": "4.57.1",
- "@rollup/rollup-linux-arm64-musl": "4.57.1",
- "@rollup/rollup-linux-loong64-gnu": "4.57.1",
- "@rollup/rollup-linux-loong64-musl": "4.57.1",
- "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
- "@rollup/rollup-linux-ppc64-musl": "4.57.1",
- "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
- "@rollup/rollup-linux-riscv64-musl": "4.57.1",
- "@rollup/rollup-linux-s390x-gnu": "4.57.1",
- "@rollup/rollup-linux-x64-gnu": "4.57.1",
- "@rollup/rollup-linux-x64-musl": "4.57.1",
- "@rollup/rollup-openbsd-x64": "4.57.1",
- "@rollup/rollup-openharmony-arm64": "4.57.1",
- "@rollup/rollup-win32-arm64-msvc": "4.57.1",
- "@rollup/rollup-win32-ia32-msvc": "4.57.1",
- "@rollup/rollup-win32-x64-gnu": "4.57.1",
- "@rollup/rollup-win32-x64-msvc": "4.57.1",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/saxes": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
- "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
- "dev": true,
- "dependencies": {
- "xmlchars": "^2.2.0"
- },
- "engines": {
- "node": ">=v12.22.7"
- }
- },
- "node_modules/scheduler": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
- "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="
- },
- "node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/set-cookie-parser": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
- "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/siginfo": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
- "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
- "dev": true
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/stackback": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
- "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
- "dev": true
- },
- "node_modules/std-env": {
- "version": "3.10.0",
- "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
- "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
- "dev": true
- },
- "node_modules/strip-indent": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
- "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
- "dev": true,
- "dependencies": {
- "min-indent": "^1.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/symbol-tree": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
- "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
- "dev": true
- },
- "node_modules/synckit": {
- "version": "0.11.12",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
- "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
- "dev": true,
- "dependencies": {
- "@pkgr/core": "^0.2.9"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/synckit"
- }
- },
- "node_modules/tinybench": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
- "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
- "dev": true
- },
- "node_modules/tinyexec": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
- "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
- "dev": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tinyglobby": {
- "version": "0.2.15",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
- "dev": true,
- "dependencies": {
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
- }
- },
- "node_modules/tinyrainbow": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
- "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
- "dev": true,
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/tldts": {
- "version": "7.0.23",
- "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz",
- "integrity": "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==",
- "dev": true,
- "dependencies": {
- "tldts-core": "^7.0.23"
- },
- "bin": {
- "tldts": "bin/cli.js"
- }
- },
- "node_modules/tldts-core": {
- "version": "7.0.23",
- "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.23.tgz",
- "integrity": "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==",
- "dev": true
- },
- "node_modules/tough-cookie": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz",
- "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
- "dev": true,
- "dependencies": {
- "tldts": "^7.0.5"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/tr46": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
- "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
- "dev": true,
- "dependencies": {
- "punycode": "^2.3.1"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/ts-api-utils": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
- "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
- "dev": true,
- "engines": {
- "node": ">=18.12"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4"
- }
- },
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
- "dev": true,
- "dependencies": {
- "prelude-ls": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/typescript-eslint": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz",
- "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/eslint-plugin": "8.55.0",
- "@typescript-eslint/parser": "8.55.0",
- "@typescript-eslint/typescript-estree": "8.55.0",
- "@typescript-eslint/utils": "8.55.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/undici": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.21.0.tgz",
- "integrity": "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==",
- "dev": true,
- "engines": {
- "node": ">=20.18.1"
- }
- },
- "node_modules/undici-types": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
- "dev": true
- },
- "node_modules/update-browserslist-db": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
- }
- },
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/vite": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
- "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
- "dev": true,
- "dependencies": {
- "esbuild": "^0.27.0",
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3",
- "postcss": "^8.5.6",
- "rollup": "^4.43.0",
- "tinyglobby": "^0.2.15"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^20.19.0 || >=22.12.0",
- "jiti": ">=1.21.0",
- "less": "^4.0.0",
- "lightningcss": "^1.21.0",
- "sass": "^1.70.0",
- "sass-embedded": "^1.70.0",
- "stylus": ">=0.54.8",
- "sugarss": "^5.0.0",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "jiti": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- },
- "node_modules/vitest": {
- "version": "4.0.18",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
- "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
- "dev": true,
- "dependencies": {
- "@vitest/expect": "4.0.18",
- "@vitest/mocker": "4.0.18",
- "@vitest/pretty-format": "4.0.18",
- "@vitest/runner": "4.0.18",
- "@vitest/snapshot": "4.0.18",
- "@vitest/spy": "4.0.18",
- "@vitest/utils": "4.0.18",
- "es-module-lexer": "^1.7.0",
- "expect-type": "^1.2.2",
- "magic-string": "^0.30.21",
- "obug": "^2.1.1",
- "pathe": "^2.0.3",
- "picomatch": "^4.0.3",
- "std-env": "^3.10.0",
- "tinybench": "^2.9.0",
- "tinyexec": "^1.0.2",
- "tinyglobby": "^0.2.15",
- "tinyrainbow": "^3.0.3",
- "vite": "^6.0.0 || ^7.0.0",
- "why-is-node-running": "^2.3.0"
- },
- "bin": {
- "vitest": "vitest.mjs"
- },
- "engines": {
- "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "@edge-runtime/vm": "*",
- "@opentelemetry/api": "^1.9.0",
- "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
- "@vitest/browser-playwright": "4.0.18",
- "@vitest/browser-preview": "4.0.18",
- "@vitest/browser-webdriverio": "4.0.18",
- "@vitest/ui": "4.0.18",
- "happy-dom": "*",
- "jsdom": "*"
- },
- "peerDependenciesMeta": {
- "@edge-runtime/vm": {
- "optional": true
- },
- "@opentelemetry/api": {
- "optional": true
- },
- "@types/node": {
- "optional": true
- },
- "@vitest/browser-playwright": {
- "optional": true
- },
- "@vitest/browser-preview": {
- "optional": true
- },
- "@vitest/browser-webdriverio": {
- "optional": true
- },
- "@vitest/ui": {
- "optional": true
- },
- "happy-dom": {
- "optional": true
- },
- "jsdom": {
- "optional": true
- }
- }
- },
- "node_modules/w3c-xmlserializer": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
- "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
- "dev": true,
- "dependencies": {
- "xml-name-validator": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/webidl-conversions": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
- "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
- "dev": true,
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/whatwg-mimetype": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
- "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
- "dev": true,
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/whatwg-url": {
- "version": "16.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.0.tgz",
- "integrity": "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==",
- "dev": true,
- "dependencies": {
- "@exodus/bytes": "^1.11.0",
- "tr46": "^6.0.0",
- "webidl-conversions": "^8.0.1"
- },
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- }
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/why-is-node-running": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
- "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
- "dev": true,
- "dependencies": {
- "siginfo": "^2.0.0",
- "stackback": "0.0.2"
- },
- "bin": {
- "why-is-node-running": "cli.js"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/word-wrap": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/xml-name-validator": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
- "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
- "dev": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/xmlchars": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
- "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
- "dev": true
- },
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/zod": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
- "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
- "dev": true,
- "funding": {
- "url": "https://github.com/sponsors/colinhacks"
- }
- },
- "node_modules/zod-validation-error": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
- "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
- "dev": true,
- "engines": {
- "node": ">=18.0.0"
- },
- "peerDependencies": {
- "zod": "^3.25.0 || ^4.0.0"
- }
- }
- }
-}
diff --git a/frontend/package.json b/frontend/package.json
deleted file mode 100644
index 4bed8f9..0000000
--- a/frontend/package.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- "name": "frontend",
- "private": true,
- "version": "0.0.0",
- "type": "module",
- "scripts": {
- "dev": "vite",
- "build": "tsc -b && vite build",
- "lint": "eslint . --max-warnings 0",
- "format": "prettier --write .",
- "preview": "vite preview",
- "test": "vitest",
- "test:e2e": "playwright test",
- "test:mocked": "playwright test tests/mocked-api.spec.ts",
- "coverage": "vitest run --coverage"
- },
- "dependencies": {
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
- "react-router-dom": "^7.13.0"
- },
- "devDependencies": {
- "@eslint/js": "^9.39.1",
- "@playwright/test": "^1.58.2",
- "@testing-library/jest-dom": "^6.9.1",
- "@testing-library/react": "^16.3.2",
- "@types/node": "^24.10.1",
- "@types/react": "^19.2.7",
- "@types/react-dom": "^19.2.3",
- "@vitejs/plugin-react": "^5.1.1",
- "@vitest/coverage-v8": "^4.0.18",
- "eslint": "^9.39.1",
- "eslint-config-prettier": "^10.1.8",
- "eslint-plugin-prettier": "^5.5.5",
- "eslint-plugin-react-hooks": "^7.0.1",
- "eslint-plugin-react-refresh": "^0.4.24",
- "globals": "^16.5.0",
- "jsdom": "^28.0.0",
- "prettier": "^3.8.1",
- "typescript": "~5.9.3",
- "typescript-eslint": "^8.48.0",
- "vite": "^7.3.1",
- "vitest": "^4.0.18"
- }
-} \ No newline at end of file
diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts
deleted file mode 100644
index 26e0b59..0000000
--- a/frontend/playwright.config.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { defineConfig, devices } from '@playwright/test';
-
-export default defineConfig({
- testDir: './tests',
- fullyParallel: true,
- forbidOnly: !!process.env.CI,
- retries: process.env.CI ? 2 : 0,
- workers: 1, // Avoid VM crash
- reporter: 'html',
- use: {
- baseURL: 'http://localhost:5173', // Vite dev server
- trace: 'on-first-retry',
- },
- projects: [
- {
- name: 'chromium',
- use: { ...devices['Desktop Chrome'] },
- },
- ],
- webServer: {
- command: 'npm run dev',
- url: 'http://localhost:5173',
- reuseExistingServer: !process.env.CI,
- },
-});
diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/frontend/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg> \ No newline at end of file
diff --git a/frontend/src/App.css b/frontend/src/App.css
deleted file mode 100644
index 0723023..0000000
--- a/frontend/src/App.css
+++ /dev/null
@@ -1,136 +0,0 @@
-/* Resets and Base Styles */
-* {
- box-sizing: border-box;
-}
-
-body {
- margin: 0;
-}
-
-/* Dashboard Layout */
-.dashboard {
- display: flex;
- flex-direction: column;
- height: 100vh;
- height: 100dvh;
- width: 100%;
- overflow: hidden;
- /* Prevent body scroll */
-}
-
-/* Header styles removed as we moved to sidebar navigation */
-
-.dashboard-content {
- display: flex;
- flex: 1;
- overflow: hidden;
- position: relative;
- width: 100%;
-}
-
-.dashboard-sidebar {
- width: 11rem;
- background: transparent;
- border-right: 1px solid var(--border-color);
- display: flex;
- flex-direction: column;
- overflow-y: auto;
- transition: margin-left 0.4s ease;
- /* No padding here, handled in FeedList */
-}
-
-.dashboard-sidebar.hidden {
- margin-left: -11rem;
-}
-
-.dashboard-main {
- flex: 1;
- min-width: 0;
- padding: 2rem;
- overflow-y: auto;
- overflow-x: hidden;
- background: var(--bg-color);
- margin-left: 0;
-}
-
-.dashboard-main>* {
- max-width: 35em;
- margin: 0 auto;
-}
-
-.fixed-toggle {
- position: absolute;
- top: 1rem;
- left: 1rem;
- z-index: 1000;
- background: var(--bg-color);
- /* Added bg to be visible over content if needed */
- background: none;
- border: none;
- font-size: 2rem;
- line-height: 1;
- cursor: pointer;
- padding: 0.2rem;
- color: var(--text-color);
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.fixed-toggle:hover {
- transform: scale(1.1);
-}
-
-/* Mobile Responsiveness */
-@media (max-width: 768px) {
- .dashboard-sidebar {
- position: fixed;
- top: 0;
- left: 0;
- bottom: 0;
- z-index: 1100;
- box-shadow: 2px 0 10px rgba(0, 0, 0, 0.2);
- width: 14rem;
- /* Slightly wider on mobile for better target area */
- }
-
- .dashboard-sidebar.hidden {
- margin-left: -14rem;
- }
-
- .dashboard-main {
- padding: 1rem;
- padding-top: 4rem;
- /* Space for the toggle button */
- }
-
- .dashboard-main>* {
- max-width: 100%;
- }
-
- /* When sidebar is visible on mobile, we show a backdrop */
- .sidebar-backdrop {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.4);
- z-index: 1050;
- animation: fadeIn 0.3s ease;
- }
-
- .dashboard.sidebar-visible::after {
- display: none;
- }
-}
-
-@keyframes fadeIn {
- from {
- opacity: 0;
- }
-
- to {
- opacity: 1;
- }
-} \ No newline at end of file
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
deleted file mode 100644
index 27b9da2..0000000
--- a/frontend/src/App.test.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import React from 'react';
-import '@testing-library/jest-dom';
-import { render, screen, waitFor, fireEvent } from '@testing-library/react';
-import App from './App';
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-
-describe('App', () => {
- beforeEach(() => {
- vi.resetAllMocks();
- global.fetch = vi.fn();
- });
-
- it('renders login on initial load (unauthenticated)', async () => {
- vi.mocked(global.fetch).mockResolvedValueOnce({
- ok: false,
- } as Response);
- window.history.pushState({}, 'Test page', '/v2/login');
- render(<App />);
- expect(await screen.findByRole('button', { name: /login/i })).toBeInTheDocument();
- });
-
- it('renders dashboard when authenticated', async () => {
- vi.mocked(global.fetch).mockImplementation((url) => {
- const urlStr = url.toString();
- if (urlStr.includes('/api/auth')) return Promise.resolve({ ok: true } as Response);
- if (urlStr.includes('/api/feed/')) return Promise.resolve({ ok: true, json: async () => [] } as Response);
- if (urlStr.includes('/api/tag')) return Promise.resolve({ ok: true, json: async () => [] } as Response);
- return Promise.resolve({ ok: true } as Response); // Fallback
- });
-
- window.history.pushState({}, 'Test page', '/v2/');
- render(<App />);
-
- await waitFor(() => {
- expect(screen.getByText('🐱')).toBeInTheDocument();
- });
-
- // Test Logout
- const logoutBtn = screen.getByText(/logout/i);
- expect(logoutBtn).toBeInTheDocument();
-
- // Mock window.location
- Object.defineProperty(window, 'location', {
- configurable: true,
- value: { href: '' },
- });
-
- vi.mocked(global.fetch).mockResolvedValueOnce({ ok: true } as Response);
-
- fireEvent.click(logoutBtn);
-
- await waitFor(() => {
- expect(global.fetch).toHaveBeenCalledWith(
- '/api/logout',
- expect.objectContaining({ method: 'POST' })
- );
- expect(window.location.href).toBe('/v2/#/login');
- });
- });
-});
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
deleted file mode 100644
index cc45949..0000000
--- a/frontend/src/App.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { HashRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom';
-import Login from './components/Login';
-import './App.css';
-import { apiFetch } from './utils';
-
-// Protected Route wrapper
-function RequireAuth({ children }: { children: React.ReactElement }) {
- const [auth, setAuth] = useState<boolean | null>(null);
- const location = useLocation();
-
- useEffect(() => {
- apiFetch('/api/auth')
- .then((res) => {
- if (res.ok) {
- setAuth(true);
- } else {
- setAuth(false);
- }
- })
- .catch(() => setAuth(false));
- }, []);
-
- if (auth === null) {
- return <div>Loading...</div>;
- }
-
- if (!auth) {
- return <Navigate to="/login" state={{ from: location }} replace />;
- }
-
- return children;
-}
-
-import FeedList from './components/FeedList';
-import FeedItems from './components/FeedItems';
-import Settings from './components/Settings';
-
-interface DashboardProps {
- theme: string;
- setTheme: (t: string) => void;
- fontTheme: string;
- setFontTheme: (t: string) => void;
-}
-
-function Dashboard({ theme, setTheme, fontTheme, setFontTheme }: DashboardProps) {
- const [sidebarVisible, setSidebarVisible] = useState(window.innerWidth > 768);
-
- useEffect(() => {
- const handleResize = () => {
- if (window.innerWidth > 768) {
- setSidebarVisible(true);
- } else {
- setSidebarVisible(false);
- }
- };
- window.addEventListener('resize', handleResize);
- return () => window.removeEventListener('resize', handleResize);
- }, []);
-
- return (
- <div
- className={`dashboard ${sidebarVisible ? 'sidebar-visible' : 'sidebar-hidden'} theme-${theme} font-${fontTheme}`}
- >
- <div className="dashboard-content">
- {(!sidebarVisible || window.innerWidth <= 768) && (
- <button
- className="sidebar-toggle fixed-toggle"
- onClick={() => setSidebarVisible(!sidebarVisible)}
- title={sidebarVisible ? "Hide Sidebar" : "Show Sidebar"}
- >
- 🐱
- </button>
- )}
- {sidebarVisible && (
- <div
- className="sidebar-backdrop"
- onClick={() => setSidebarVisible(false)}
- />
- )}
- <aside className={`dashboard-sidebar ${sidebarVisible ? '' : 'hidden'}`}>
- <FeedList
- theme={theme}
- setTheme={setTheme}
- setSidebarVisible={setSidebarVisible}
- isMobile={window.innerWidth <= 768}
- />
- </aside>
- <main className="dashboard-main">
- <Routes>
- <Route path="/feed/:feedId" element={<FeedItems />} />
- <Route path="/tag/:tagName" element={<FeedItems />} />
- <Route path="/settings" element={<Settings fontTheme={fontTheme} setFontTheme={setFontTheme} />} />
- <Route path="/" element={<FeedItems />} />
- </Routes>
- </main>
- </div>
- </div>
- );
-}
-
-function App() {
- const [theme, setTheme] = useState(localStorage.getItem('neko-theme') || 'light');
- const [fontTheme, setFontTheme] = useState(localStorage.getItem('neko-font-theme') || 'default');
-
- const handleSetTheme = (newTheme: string) => {
- setTheme(newTheme);
- localStorage.setItem('neko-theme', newTheme);
- };
-
- const handleSetFontTheme = (newFontTheme: string) => {
- setFontTheme(newFontTheme);
- localStorage.setItem('neko-font-theme', newFontTheme);
- };
-
-
-
- return (
- <HashRouter>
- <Routes>
- <Route path="/login" element={<Login />} />
- <Route
- path="/*"
- element={
- <RequireAuth>
- <Dashboard
- theme={theme}
- setTheme={handleSetTheme}
- fontTheme={fontTheme}
- setFontTheme={handleSetFontTheme}
- />
- </RequireAuth>
- }
- />
- </Routes>
- </HashRouter>
- );
-}
-
-export default App;
diff --git a/frontend/src/Navigation.test.tsx b/frontend/src/Navigation.test.tsx
deleted file mode 100644
index b0bae86..0000000
--- a/frontend/src/Navigation.test.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
-import React from 'react';
-import App from './App';
-import '@testing-library/jest-dom';
-
-describe('Navigation and Filtering', () => {
- beforeEach(() => {
- vi.resetAllMocks();
- global.fetch = vi.fn();
- // Default mock response for auth
- vi.mocked(global.fetch).mockImplementation((url) => {
- const urlStr = url.toString();
- if (urlStr.includes('/api/auth')) return Promise.resolve({ ok: true, json: async () => ({ status: 'ok' }) } as Response);
- if (urlStr.includes('/api/feed/')) return Promise.resolve({
- ok: true,
- json: async () => [
- { _id: 1, title: 'Feed 1', url: 'http://f1.com' },
- { _id: 2, title: 'Feed 2', url: 'http://f2.com' }
- ]
- } as Response);
- if (urlStr.includes('/api/tag')) return Promise.resolve({ ok: true, json: async () => [] } as Response);
- if (urlStr.includes('/api/stream')) return Promise.resolve({ ok: true, json: async () => [] } as Response);
- return Promise.resolve({ ok: true, json: async () => ({}) } as Response);
- });
- });
-
- it('preserves "all" filter when clicking a feed', async () => {
- Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: 1024 });
- window.history.pushState({}, '', '/#/');
- render(<App />);
-
- // Wait for sidebar to load and feeds section to be visible
- await waitFor(() => {
- expect(screen.queryByText(/Loading feeds/i)).not.toBeInTheDocument();
- });
-
- // Expand feeds if not expanded
- const feedsHeader = await screen.findByRole('heading', { name: /Feeds/i, level: 4 });
- fireEvent.click(feedsHeader);
-
- await waitFor(() => {
- expect(screen.getByText('Feed 1')).toBeInTheDocument();
- });
- // Click 'all' filter
- const allFilter = screen.getByText('all');
- fireEvent.click(allFilter);
-
- // Verify URL has filter=all
- await waitFor(() => {
- expect(window.location.hash).toContain('filter=all');
- });
-
- // Click Feed 1
- const feed1Link = screen.getByText('Feed 1');
- fireEvent.click(feed1Link);
-
- // Verify URL is /feed/1?filter=all (or similar)
- await waitFor(() => {
- expect(window.location.hash).toContain('/feed/1');
- expect(window.location.hash).toContain('filter=all');
- });
-
- // Click Feed 2
- const feed2Link = screen.getByText('Feed 2');
- fireEvent.click(feed2Link);
-
- // Verify URL is /feed/2?filter=all
- await waitFor(() => {
- expect(window.location.hash).toContain('/feed/2');
- expect(window.location.hash).toContain('filter=all');
- });
- });
-
- it('highlights the correct filter link', async () => {
- window.history.pushState({}, '', '/#/');
- render(<App />);
-
- await waitFor(() => {
- const unreadLink = screen.getByText('unread');
- expect(unreadLink.className).toContain('active');
- });
-
- fireEvent.click(screen.getByText('all'));
- await waitFor(() => {
- const allLink = screen.getByText('all');
- const unreadLink = screen.getByText('unread');
- expect(allLink.className).toContain('active');
- expect(unreadLink.className).not.toContain('active');
- });
- });
-
- it('highlights "unread" as active even when on a feed page without filter param', async () => {
- window.history.pushState({}, '', '/#/feed/1');
- render(<App />);
-
- await waitFor(() => {
- const unreadLink = screen.getByText('unread');
- expect(unreadLink.className).toContain('active');
- });
- });
-
- it('preserves search query when clicking a feed', async () => {
- window.history.pushState({}, '', '/#/?q=linux');
- render(<App />);
-
- // Wait for load
- await waitFor(() => {
- expect(screen.queryByText(/Loading feeds/i)).not.toBeInTheDocument();
- });
-
- const feedsHeader = await screen.findByRole('heading', { name: /Feeds/i, level: 4 });
- fireEvent.click(feedsHeader);
-
- await screen.findByText('Feed 1');
- fireEvent.click(screen.getByText('Feed 1'));
-
- await waitFor(() => {
- expect(window.location.hash).toContain('/feed/1');
- expect(window.location.hash).toContain('q=linux');
- });
- });
-});
diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg
deleted file mode 100644
index 6c87de9..0000000
--- a/frontend/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg> \ No newline at end of file
diff --git a/frontend/src/components/FeedItem.css b/frontend/src/components/FeedItem.css
deleted file mode 100644
index 876fc66..0000000
--- a/frontend/src/components/FeedItem.css
+++ /dev/null
@@ -1,158 +0,0 @@
-.feed-item {
- padding: 1rem;
- margin-top: 5rem;
- list-style: none;
- border-bottom: none;
-}
-
-/* removed read/unread specific font-weight to keep it always bold as requested */
-
-.item-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 0.5rem;
-}
-
-.item-title {
- font-family: var(--font-heading);
- font-size: 1.8rem;
- font-weight: bold;
- text-decoration: none;
- color: var(--link-color);
- display: block;
- flex: 1;
-}
-
-.item-title:hover {
- text-decoration: none;
- color: var(--link-color);
-}
-
-.item-actions {
- display: flex;
- gap: 0.5rem;
- margin-left: 1rem;
-}
-
-/* Legacy controls were simple text/links, but buttons are fine if minimal */
-.star-btn {
- background: none;
- border: none;
- cursor: pointer;
- font-size: 1.25rem;
- padding: 0 0 0 0.5rem;
- vertical-align: middle;
- transition: color 0.2s;
- line-height: 1;
-}
-
-.star-btn.is-starred {
- color: blue;
-}
-
-.star-btn.is-unstarred {
- color: var(--text-color);
- opacity: 0.3;
-}
-
-.star-btn:hover {
- color: blue;
-}
-
-.action-btn {
- background: var(--sidebar-bg);
- border: 1px solid var(--border-color, #ccc);
- cursor: pointer;
- padding: 2px 6px;
- font-size: 1rem;
- color: blue;
- font-weight: bold;
-}
-
-.action-btn:hover {
- background-color: #eee;
-}
-
-.dateline {
- margin-top: 0;
- font-weight: normal;
- font-size: 0.75em;
- color: #ccc;
- margin-bottom: 1rem;
-}
-
-.dateline a {
- color: #ccc;
- text-decoration: none;
-}
-
-.item-description {
- color: var(--text-color);
- line-height: 1.5;
- font-size: 1rem;
- margin-top: 1rem;
- overflow-wrap: break-word;
- word-break: break-word;
-}
-
-.item-description table,
-.item-description pre,
-.item-description code {
- max-width: 100%;
- overflow-x: auto;
- display: block;
-}
-
-.item-description img {
- max-width: 100%;
- height: auto;
- display: block;
- margin: 1rem 0;
-}
-
-.item-description blockquote {
- padding: 1rem 1rem 0 1rem;
- border-left: 4px solid var(--sidebar-bg);
- color: var(--text-color);
- opacity: 0.8;
- margin-left: 0;
-}
-
-.scrape-btn {
- background: var(--bg-color);
- border: 1px solid var(--border-color, #ccc);
- color: blue;
- cursor: pointer;
- font-family: var(--font-heading);
- font-weight: bold;
- font-size: 0.8rem;
- padding: 2px 6px;
- margin-left: 0.5rem;
-}
-
-.scrape-btn:hover {
- background: var(--sidebar-bg);
-}
-
-@media (max-width: 768px) {
- .feed-item {
- margin-top: 2rem;
- padding: 0.5rem;
- }
-
- .item-title {
- font-size: 1.4rem;
- word-break: break-word;
- }
-
- .item-header {
- flex-direction: column;
- gap: 0.5rem;
- }
-
- .item-actions {
- margin-left: 0;
- margin-bottom: 0.5rem;
- }
-} \ No newline at end of file
diff --git a/frontend/src/components/FeedItem.test.tsx b/frontend/src/components/FeedItem.test.tsx
deleted file mode 100644
index ab2ca45..0000000
--- a/frontend/src/components/FeedItem.test.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import React from 'react';
-import '@testing-library/jest-dom';
-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import FeedItem from './FeedItem';
-import type { Item } from '../types';
-
-const mockItem: Item = {
- _id: 1,
- feed_id: 101,
- title: 'Test Item',
- url: 'http://example.com/item',
- description: '<p>Description</p>',
- publish_date: '2023-01-01',
- read: false,
- starred: false,
- feed_title: 'Test Feed',
-};
-
-describe('FeedItem Component', () => {
- beforeEach(() => {
- vi.resetAllMocks();
- global.fetch = vi.fn();
- });
-
- it('renders item details', () => {
- render(<FeedItem item={mockItem} />);
- expect(screen.getByText('Test Item')).toBeInTheDocument();
- expect(screen.getByText(/Test Feed/)).toBeInTheDocument();
- });
-
- it('calls onToggleStar when star clicked', () => {
- const onToggleStar = vi.fn();
- render(<FeedItem item={mockItem} onToggleStar={onToggleStar} />);
-
- const starBtn = screen.getByTitle('Star');
- fireEvent.click(starBtn);
-
- expect(onToggleStar).toHaveBeenCalledWith(mockItem);
- });
-
- it('updates styling when read state changes', () => {
- const { rerender } = render(<FeedItem item={{ ...mockItem, read: false }} />);
- const link = screen.getByText('Test Item');
- const listItem = link.closest('li');
- expect(listItem).toHaveClass('unread');
- expect(listItem).not.toHaveClass('read');
-
- rerender(<FeedItem item={{ ...mockItem, read: true }} />);
- expect(listItem).toHaveClass('read');
- expect(listItem).not.toHaveClass('unread');
- });
-
- it('loads full content and calls onUpdate', async () => {
- const onUpdate = vi.fn();
- vi.mocked(global.fetch).mockResolvedValueOnce({
- ok: true,
- json: async () => ({ full_content: '<p>Full Content Loaded</p>' }),
- } as Response);
-
- const { rerender } = render(<FeedItem item={mockItem} onUpdate={onUpdate} />);
-
- const scrapeBtn = screen.getByTitle('Load Full Content');
- fireEvent.click(scrapeBtn);
-
- await waitFor(() => {
- expect(global.fetch).toHaveBeenCalledWith('/api/item/1', expect.anything());
- });
-
- await waitFor(() => {
- expect(onUpdate).toHaveBeenCalledWith(expect.objectContaining({
- full_content: '<p>Full Content Loaded</p>'
- }));
- });
-
- // Simulate parent updating prop
- rerender(<FeedItem item={{ ...mockItem, full_content: '<p>Full Content Loaded</p>' }} onUpdate={onUpdate} />);
- expect(screen.getByText('Full Content Loaded')).toBeInTheDocument();
- });
-});
diff --git a/frontend/src/components/FeedItem.tsx b/frontend/src/components/FeedItem.tsx
deleted file mode 100644
index 865c080..0000000
--- a/frontend/src/components/FeedItem.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { useState, memo } from 'react';
-import type { Item } from '../types';
-import './FeedItem.css';
-
-import { apiFetch } from '../utils';
-
-interface FeedItemProps {
- item: Item;
- onToggleStar?: (item: Item) => void;
- onUpdate?: (item: Item) => void;
-}
-
-const FeedItem = memo(function FeedItem({ item, onToggleStar, onUpdate }: FeedItemProps) {
- const [loading, setLoading] = useState(false);
-
- // We rely on props.item for data.
- // If we fetch full content, we notify the parent via onUpdate.
-
- const handleToggleStar = (e: React.MouseEvent) => {
- e.stopPropagation();
- if (onToggleStar) {
- onToggleStar(item);
- } else {
- // Fallback if no handler passed (backward compat or isolated usage)
- // But really we should rely on parent.
- // For now, let's keep the optimistic local update logic if we were standalone,
- // but since we are optimizing, we assume parent handles it.
- }
- };
-
- const loadFullContent = (e: React.MouseEvent) => {
- e.stopPropagation();
- setLoading(true);
- apiFetch(`/api/item/${item._id}`)
- .then((res) => {
- if (!res.ok) throw new Error('Failed to fetch full content');
- return res.json();
- })
- .then((data) => {
- // Merge the new data (full_content) into the item and notify parent
- const newItem = { ...item, ...data };
- if (onUpdate) {
- onUpdate(newItem);
- }
- setLoading(false);
- })
- .catch((err) => {
- console.error('Error fetching full content:', err);
- setLoading(false);
- });
- };
-
- return (
- <li className={`feed-item ${item.read ? 'read' : 'unread'} ${loading ? 'loading' : ''}`}>
- <div className="item-header">
- <a href={item.url} target="_blank" rel="noopener noreferrer" className="item-title">
- {item.title || '(No Title)'}
- </a>
- <button
- onClick={handleToggleStar}
- className={`star-btn ${item.starred ? 'is-starred' : 'is-unstarred'}`}
- title={item.starred ? 'Unstar' : 'Star'}
- >
- ★
- </button>
- </div>
- <div className="dateline">
- <a href={item.url} target="_blank" rel="noopener noreferrer">
- {new Date(item.publish_date).toLocaleDateString()}
- {item.feed_title && ` - ${item.feed_title}`}
- </a>
- <div className="item-actions" style={{ display: 'inline-block', float: 'right' }}>
- {!item.full_content && (
- <button onClick={loadFullContent} className="scrape-btn" title="Load Full Content">
- text
- </button>
- )}
- </div>
- </div>
- {(item.full_content || item.description) && (
- <div
- className="item-description"
- dangerouslySetInnerHTML={{ __html: item.full_content || item.description }}
- />
- )}
- </li>
- );
-});
-
-export default FeedItem;
diff --git a/frontend/src/components/FeedItems.css b/frontend/src/components/FeedItems.css
deleted file mode 100644
index 7154ac2..0000000
--- a/frontend/src/components/FeedItems.css
+++ /dev/null
@@ -1,23 +0,0 @@
-.feed-items {
- padding: 1rem 0;
- /* Removing horizontal padding to avoid double-padding with FeedItem */
-}
-
-.feed-items h2 {
- margin-top: 0;
- border-bottom: 2px solid var(--border-color);
- padding-bottom: 0.5rem;
-}
-
-.item-list {
- list-style: none;
- padding: 0;
-}
-
-.loading-more {
- padding: 2rem;
- text-align: center;
- color: #888;
- font-size: 0.9rem;
- min-height: 50px;
-} \ No newline at end of file
diff --git a/frontend/src/components/FeedItems.test.tsx b/frontend/src/components/FeedItems.test.tsx
deleted file mode 100644
index 1a002d8..0000000
--- a/frontend/src/components/FeedItems.test.tsx
+++ /dev/null
@@ -1,250 +0,0 @@
-import React from 'react';
-import '@testing-library/jest-dom';
-import { render, screen, waitFor, fireEvent, act } from '@testing-library/react';
-import { MemoryRouter, Route, Routes } from 'react-router-dom';
-import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
-import FeedItems from './FeedItems';
-
-describe('FeedItems Component', () => {
- beforeEach(() => {
- vi.resetAllMocks();
- global.fetch = vi.fn();
- window.HTMLElement.prototype.scrollIntoView = vi.fn();
-
- // Mock IntersectionObserver
- class MockIntersectionObserver {
- observe = vi.fn();
- unobserve = vi.fn();
- disconnect = vi.fn();
- }
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- window.IntersectionObserver = MockIntersectionObserver as any;
- });
-
- it('renders loading state', () => {
- vi.mocked(global.fetch).mockImplementation(() => new Promise(() => { }));
- render(
- <MemoryRouter initialEntries={['/feed/1']}>
- <Routes>
- <Route path="/feed/:feedId" element={<FeedItems />} />
- </Routes>
- </MemoryRouter>
- );
- expect(screen.getByText(/loading items/i)).toBeInTheDocument();
- });
-
- it('renders items for a feed', async () => {
- const mockItems = [
- {
- _id: 101,
- title: 'Item One',
- url: 'http://example.com/1',
- publish_date: '2023-01-01',
- read: false,
- },
- {
- _id: 102,
- title: 'Item Two',
- url: 'http://example.com/2',
- publish_date: '2023-01-02',
- read: true,
- },
- ];
-
- vi.mocked(global.fetch).mockResolvedValueOnce({
- ok: true,
- json: async () => mockItems,
- } as Response);
-
- render(
- <MemoryRouter initialEntries={['/feed/1']}>
- <Routes>
- <Route path="/feed/:feedId" element={<FeedItems />} />
- </Routes>
- </MemoryRouter>
- );
-
- await waitFor(() => {
- expect(screen.getByText('Item One')).toBeInTheDocument();
- });
-
- const params = new URLSearchParams();
- params.append('feed_id', '1');
- params.append('read_filter', 'unread');
- expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`, expect.anything());
- });
-
-
-
- afterEach(() => {
- vi.useRealTimers();
- vi.restoreAllMocks();
- });
-
- it('marks items as read when scrolled past', async () => {
- const mockItems = [{ _id: 101, title: 'Item 1', url: 'u1', read: false, starred: false }];
- vi.mocked(global.fetch).mockResolvedValue({
- ok: true,
- json: async () => mockItems,
- } as Response);
-
- // Mock getBoundingClientRect
- const getBoundingClientRectMock = vi.spyOn(Element.prototype, 'getBoundingClientRect');
- getBoundingClientRectMock.mockImplementation(function (this: Element) {
- if (this.classList && this.classList.contains('dashboard-main')) {
- return {
- top: 0, bottom: 500, height: 500, left: 0, right: 1000, width: 1000, x: 0, y: 0,
- toJSON: () => { }
- } as DOMRect;
- }
- if (this.id && this.id.startsWith('item-')) {
- // Item top is -50 (above container top 0)
- return {
- top: -150, bottom: -50, height: 100, left: 0, right: 1000, width: 1000, x: 0, y: 0,
- toJSON: () => { }
- } as DOMRect;
- }
- return {
- top: 0, bottom: 0, height: 0, left: 0, right: 0, width: 0, x: 0, y: 0,
- toJSON: () => { }
- } as DOMRect;
- });
-
- render(
- <MemoryRouter>
- <div className="dashboard-main">
- <FeedItems />
- </div>
- </MemoryRouter>
- );
-
- // Initial load fetch
- await waitFor(() => {
- expect(screen.getByText('Item 1')).toBeVisible();
- });
-
- // Trigger scroll
- const container = document.querySelector('.dashboard-main');
- expect(container).not.toBeNull();
-
- act(() => {
- // Dispatch scroll event
- fireEvent.scroll(container!);
- });
-
- // Wait for throttle (500ms) + buffer
- await new Promise(r => setTimeout(r, 600));
-
- await waitFor(() => {
- expect(global.fetch).toHaveBeenCalledWith(
- '/api/item/101',
- expect.objectContaining({
- method: 'PUT',
- body: JSON.stringify({ read: true, starred: false }),
- })
- );
- });
- });
-
- it('loads more items when sentinel becomes visible', async () => {
- const initialItems = [{ _id: 101, title: 'Item 1', url: 'u1', read: true, starred: false }];
- const moreItems = [{ _id: 100, title: 'Item 0', url: 'u0', read: true, starred: false }];
-
- vi.mocked(global.fetch)
- .mockResolvedValueOnce({ ok: true, json: async () => initialItems } as Response)
- .mockResolvedValueOnce({ ok: true, json: async () => moreItems } as Response);
-
- const observerCallbacks: IntersectionObserverCallback[] = [];
- class MockIntersectionObserver {
- constructor(callback: IntersectionObserverCallback) {
- observerCallbacks.push(callback);
- }
- observe = vi.fn();
- unobserve = vi.fn();
- disconnect = vi.fn();
- }
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- window.IntersectionObserver = MockIntersectionObserver as any;
-
- render(
- <MemoryRouter>
- <FeedItems />
- </MemoryRouter>
- );
-
- await waitFor(() => {
- expect(screen.getByText('Item 1')).toBeInTheDocument();
- });
-
- const entry = {
- isIntersecting: true,
- target: { id: 'load-more-sentinel' } as unknown as Element,
- boundingClientRect: {} as DOMRectReadOnly,
- intersectionRatio: 1,
- time: 0,
- rootBounds: null,
- intersectionRect: {} as DOMRectReadOnly,
- } as IntersectionObserverEntry;
-
- act(() => {
- // Trigger all observers
- observerCallbacks.forEach(cb => cb([entry], {} as IntersectionObserver));
- });
-
- await waitFor(() => {
- expect(screen.getByText('Item 0')).toBeInTheDocument();
- const params = new URLSearchParams();
- params.append('max_id', '101');
- params.append('read_filter', 'unread');
- // Verify the second fetch call content
- expect(global.fetch).toHaveBeenCalledWith(
- expect.stringContaining('max_id=101'),
- expect.anything()
- );
- });
- });
-
- it('loads more items when pressing j on last item', async () => {
- const initialItems = [
- { _id: 103, title: 'Item 3', url: 'u3', read: true, starred: false },
- { _id: 102, title: 'Item 2', url: 'u2', read: true, starred: false },
- { _id: 101, title: 'Item 1', url: 'u1', read: true, starred: false },
- ];
- const moreItems = [
- { _id: 100, title: 'Item 0', url: 'u0', read: true, starred: false },
- ];
-
- vi.mocked(global.fetch)
- .mockResolvedValueOnce({ ok: true, json: async () => initialItems } as Response)
- .mockResolvedValueOnce({ ok: true, json: async () => moreItems } as Response);
-
- render(
- <MemoryRouter>
- <FeedItems />
- </MemoryRouter>
- );
-
- await waitFor(() => {
- expect(screen.getByText('Item 1')).toBeInTheDocument();
- });
-
- fireEvent.keyDown(window, { key: 'j' }); // index 0
- await waitFor(() => expect(document.getElementById('item-0')).toHaveAttribute('data-selected', 'true'));
-
- fireEvent.keyDown(window, { key: 'j' }); // index 1
- await waitFor(() => expect(document.getElementById('item-1')).toHaveAttribute('data-selected', 'true'));
-
- fireEvent.keyDown(window, { key: 'j' }); // index 2 (last item)
- await waitFor(() => expect(document.getElementById('item-2')).toHaveAttribute('data-selected', 'true'));
-
- await waitFor(() => {
- expect(screen.getByText('Item 0')).toBeInTheDocument();
- });
-
- // Check fetch call
- expect(global.fetch).toHaveBeenCalledWith(
- expect.stringContaining('max_id=101'),
- expect.anything()
- );
- });
-});
diff --git a/frontend/src/components/FeedItems.tsx b/frontend/src/components/FeedItems.tsx
deleted file mode 100644
index e38850a..0000000
--- a/frontend/src/components/FeedItems.tsx
+++ /dev/null
@@ -1,313 +0,0 @@
-import { useCallback, useEffect, useRef, useState } from 'react';
-import { useParams, useSearchParams } from 'react-router-dom';
-import type { Item } from '../types';
-import FeedItem from './FeedItem';
-import './FeedItems.css';
-import { apiFetch } from '../utils';
-
-export default function FeedItems() {
- const { feedId, tagName } = useParams<{ feedId: string; tagName: string }>();
- const [searchParams] = useSearchParams();
- const filterFn = searchParams.get('filter') || 'unread';
-
- const [items, setItems] = useState<Item[]>([]);
- const itemsRef = useRef<Item[]>([]);
- const [loading, setLoading] = useState(true);
- const [loadingMore, setLoadingMore] = useState(false);
- const loadingMoreRef = useRef(loadingMore);
- const [hasMore, setHasMore] = useState(true);
- const hasMoreRef = useRef(hasMore);
- const [error, setError] = useState('');
- const [selectedIndex, setSelectedIndex] = useState(-1);
- const selectedIndexRef = useRef(selectedIndex);
-
- // Sync refs
- useEffect(() => {
- itemsRef.current = items;
- }, [items]);
-
- useEffect(() => {
- loadingMoreRef.current = loadingMore;
- }, [loadingMore]);
-
- useEffect(() => {
- hasMoreRef.current = hasMore;
- }, [hasMore]);
-
- useEffect(() => {
- selectedIndexRef.current = selectedIndex;
- }, [selectedIndex]);
-
- const fetchItems = useCallback((maxId?: string) => {
- if (maxId) {
- setLoadingMore(true);
- } else {
- setLoading(true);
- setItems([]);
- }
- setError('');
-
- let url = '/api/stream';
- const params = new URLSearchParams();
-
- if (feedId) {
- if (feedId.includes(',')) {
- params.append('feed_ids', feedId);
- } else {
- params.append('feed_id', feedId);
- }
- } else if (tagName) {
- params.append('tag', tagName);
- }
-
- if (maxId) {
- params.append('max_id', maxId);
- }
-
- // Apply filters
- const searchQuery = searchParams.get('q');
- if (searchQuery) {
- params.append('q', searchQuery);
- }
-
- if (filterFn === 'all') {
- params.append('read_filter', 'all');
- } else if (filterFn === 'starred') {
- params.append('starred', 'true');
- params.append('read_filter', 'all');
- } else {
- // default to unread
- if (!searchQuery) {
- params.append('read_filter', 'unread');
- }
- }
-
- const queryString = params.toString();
- if (queryString) {
- url += `?${queryString}`;
- }
-
- apiFetch(url)
- .then((res) => {
- if (!res.ok) {
- throw new Error('Failed to fetch items');
- }
- return res.json();
- })
- .then((data: Item[]) => {
- if (maxId) {
- setItems((prev) => {
- const existingIds = new Set(prev.map(i => i._id));
- const newItems = data.filter(i => !existingIds.has(i._id));
- return [...prev, ...newItems];
- });
- } else {
- setItems(data);
- }
- setHasMore(data.length > 0);
- setLoading(false);
- setLoadingMore(false);
- })
- .catch((err) => {
- setError(err.message);
- setLoading(false);
- setLoadingMore(false);
- });
- }, [feedId, tagName, filterFn, searchParams]);
-
- useEffect(() => {
- fetchItems();
- setSelectedIndex(-1);
- }, [fetchItems]);
-
-
- const scrollToItem = useCallback((index: number) => {
- const element = document.getElementById(`item-${index}`);
- if (element) {
- element.scrollIntoView({ behavior: 'auto', block: 'start' });
- }
- }, []);
-
- const markAsRead = useCallback((item: Item) => {
- const updatedItem = { ...item, read: true };
- setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i)));
-
- apiFetch(`/api/item/${item._id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ read: true, starred: item.starred }),
- }).catch((err) => console.error('Failed to mark read', err));
- }, []);
-
- const toggleStar = useCallback((item: Item) => {
- const updatedItem = { ...item, starred: !item.starred };
- setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i)));
-
- apiFetch(`/api/item/${item._id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ read: item.read, starred: !item.starred }),
- }).catch((err) => console.error('Failed to toggle star', err));
- }, []);
-
- const handleUpdateItem = useCallback((updatedItem: Item) => {
- setItems((prevItems) => prevItems.map((i) => (i._id === updatedItem._id ? updatedItem : i)));
- }, []);
-
-
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- const currentItems = itemsRef.current;
- if (currentItems.length === 0) return;
-
- if (e.key === 'j') {
- const nextIndex = Math.min(selectedIndexRef.current + 1, currentItems.length - 1);
- if (nextIndex !== selectedIndexRef.current) {
- selectedIndexRef.current = nextIndex;
- setSelectedIndex(nextIndex);
- const item = currentItems[nextIndex];
- if (!item.read) {
- markAsRead(item);
- }
- scrollToItem(nextIndex);
-
- // Trigger load more if needed
- if (nextIndex === currentItems.length - 1 && hasMoreRef.current && !loadingMoreRef.current) {
- fetchItems(String(currentItems[currentItems.length - 1]._id));
- }
- } else if (hasMoreRef.current && !loadingMoreRef.current) {
- // Already at last item, but more can be loaded
- fetchItems(String(currentItems[currentItems.length - 1]._id));
- }
- } else if (e.key === 'k') {
- const nextIndex = Math.max(selectedIndexRef.current - 1, 0);
- if (nextIndex !== selectedIndexRef.current) {
- selectedIndexRef.current = nextIndex;
- setSelectedIndex(nextIndex);
- scrollToItem(nextIndex);
- }
- } else if (e.key === 's') {
- if (selectedIndexRef.current >= 0 && selectedIndexRef.current < currentItems.length) {
- toggleStar(currentItems[selectedIndexRef.current]);
- }
- }
- };
-
- window.addEventListener('keydown', handleKeyDown);
- return () => window.removeEventListener('keydown', handleKeyDown);
- }, [markAsRead, scrollToItem, toggleStar, fetchItems]);
-
-
- // Scroll listener to mark items as read
- const sentinelObserverRef = useRef<IntersectionObserver | null>(null);
-
- const checkReadStatus = useCallback(() => {
- const container = document.querySelector('.dashboard-main');
- if (!container) return;
-
- const containerRect = container.getBoundingClientRect();
- const currentItems = itemsRef.current;
-
- currentItems.forEach((item, index) => {
- if (item.read) return;
-
- const el = document.getElementById(`item-${index}`);
- if (!el) return;
-
- const rect = el.getBoundingClientRect();
-
- // Mark as read if the bottom of the item is above the top of the container
- if (rect.bottom < containerRect.top) {
- markAsRead(item);
- }
- });
- }, [markAsRead]);
-
- // Setup scroll listener
- useEffect(() => {
- const container = document.querySelector('.dashboard-main');
- if (!container) return;
-
- let timeoutId: number | null = null;
- const onScroll = () => {
- if (timeoutId === null) {
- timeoutId = window.setTimeout(() => {
- checkReadStatus();
- timeoutId = null;
- }, 250);
- }
- };
-
- container.addEventListener('scroll', onScroll);
-
- // Initial check
- checkReadStatus();
-
- return () => {
- if (timeoutId) clearTimeout(timeoutId);
- container.removeEventListener('scroll', onScroll);
- };
- }, [checkReadStatus]);
-
- // Re-check when items change (e.g. initial load or load more)
- useEffect(() => {
- checkReadStatus();
- }, [items, checkReadStatus]);
-
-
-
- useEffect(() => {
- if (sentinelObserverRef.current) sentinelObserverRef.current.disconnect();
-
- sentinelObserverRef.current = new IntersectionObserver(
- (entries) => {
- entries.forEach((entry) => {
- if (entry.isIntersecting && !loadingMoreRef.current && hasMoreRef.current && itemsRef.current.length > 0) {
- fetchItems(String(itemsRef.current[itemsRef.current.length - 1]._id));
- }
- });
- },
- { root: null, threshold: 0, rootMargin: '100px' }
- );
-
- const sentinel = document.getElementById('load-more-sentinel');
- if (sentinel) sentinelObserverRef.current.observe(sentinel);
-
- return () => sentinelObserverRef.current?.disconnect();
- }, [hasMore, fetchItems]); // removed loadingMore from deps, using ref inside. hasMore is needed for DOM presence.
-
-
- if (loading) return <div className="feed-items-loading">Loading items...</div>;
- if (error) return <div className="feed-items-error">Error: {error}</div>;
-
- return (
- <div className="feed-items">
- {items.length === 0 ? (
- <p>No items found.</p>
- ) : (
- <ul className="item-list">
- {items.map((item, index) => (
- <div
- id={`item-${index}`}
- key={item._id}
- data-index={index}
- data-selected={index === selectedIndex}
- onClick={() => setSelectedIndex(index)}
- >
- <FeedItem
- item={item}
- onToggleStar={() => toggleStar(item)}
- onUpdate={handleUpdateItem}
- />
- </div>
- ))}
- {hasMore && (
- <li id="load-more-sentinel" className="loading-more">
- {loadingMore ? 'Loading more...' : ''}
- </li>
- )}
- </ul>
- )}
- </div>
- );
-}
diff --git a/frontend/src/components/FeedList.css b/frontend/src/components/FeedList.css
deleted file mode 100644
index 38a324b..0000000
--- a/frontend/src/components/FeedList.css
+++ /dev/null
@@ -1,225 +0,0 @@
-.feed-list {
- padding: 1rem;
- font-family: var(--font-heading);
- color: #777;
- /* specific v1 color */
- font-size: 0.8rem;
- background: var(--sidebar-bg);
- min-height: 100%;
- flex: 1;
-}
-
-.feed-list h1.logo {
- font-size: 2rem;
- /* match v1 */
- margin: 0 0 1rem 0;
- line-height: 1;
- cursor: pointer;
- position: sticky;
- top: 0;
- background: var(--sidebar-bg);
- z-index: 10;
- padding-bottom: 0.5rem;
- color: var(--text-color);
-}
-
-/* Override logo color if necessary for themes */
-.theme-light .feed-list h1.logo {
- color: #333;
-}
-
-.theme-dark .feed-list h1.logo {
- color: #eee;
-}
-
-.search-section {
- margin-bottom: 1rem;
-}
-
-.search-input {
- width: 100%;
- padding: 0.25rem;
- border: 1px solid var(--border-color, #999);
- background: var(--bg-color);
- color: var(--text-color);
- font-size: 0.8rem;
- font-family: inherit;
- border-radius: 0;
- /* v1 didn't have rounded inputs usually */
-}
-
-.section-header {
- font-size: 1rem;
- /* v1 h4 size? */
- font-weight: bold;
- margin: 1rem 0 0.25rem 0;
- cursor: pointer;
- user-select: none;
- font-family: var(--font-heading);
- color: #333;
- /* Darker than list items */
- text-transform: lowercase;
- font-variant: small-caps;
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.caret {
- display: inline-block;
- font-size: 0.6rem;
- transition: transform 0.2s ease;
- color: #777;
-}
-
-.caret.expanded {
- transform: rotate(90deg);
-}
-
-.filter-list,
-.tag-list-items,
-.feed-list-items,
-.nav-list {
- list-style: none;
- padding: 0;
- margin: 0;
-}
-
-.filter-list li,
-.nav-list li {
- margin-bottom: 0.1rem;
-}
-
-.filter-list a,
-.nav-list a,
-.tag-link,
-.feed-title,
-.logout-link {
- text-decoration: none;
- color: var(--link-color, blue);
- font-size: 0.8rem;
- /* Matches v1 .75em approx */
- display: block;
- cursor: pointer;
- background: none;
- border: none;
- padding: 0;
- font-family: inherit;
- font-variant: small-caps;
- text-transform: lowercase;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.filter-list a:hover,
-.nav-list a:hover,
-.tag-link:hover,
-.feed-title:hover,
-.logout-link:hover {
- text-decoration: underline;
- color: var(--link-color, blue);
-}
-
-.filter-list a.active,
-.tag-link.active,
-.feed-title.active {
- font-weight: bold;
- color: #000;
- /* Active state black */
-}
-
-.tag-item,
-.sidebar-feed-item {
- margin-bottom: 0;
-}
-
-.feed-item-row {
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.feed-checkbox {
- cursor: pointer;
- margin: 0;
-}
-
-.feed-category {
- display: none;
-}
-
-.nav-section {
- margin-top: 2rem;
- border-top: 1px solid var(--border-color, #eee);
- padding-top: 1rem;
-}
-
-.logout-link {
- text-align: left;
- width: 100%;
- color: #777;
- display: block;
-}
-
-.nav-link,
-.logout-link {
- padding: 0.25rem 0;
-}
-
-.logout-link:hover {
- color: var(--link-color, blue);
- text-decoration: underline;
-}
-
-.theme-section {
- margin-top: 1rem;
-}
-
-.theme-selector {
- display: flex;
- gap: 0.5rem;
- margin-top: 0.5rem;
-}
-
-.theme-selector button {
- background: rgba(0, 0, 0, 0.05);
- border: none;
- cursor: pointer;
- padding: 0.4rem;
- font-size: 1rem;
- border-radius: 8px;
- line-height: 1;
- transition: all 0.2s ease;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.theme-selector button:hover {
- background: rgba(0, 0, 0, 0.1);
- transform: translateY(-2px);
-}
-
-.theme-selector button.active {
- background: var(--border-color, #999);
- color: white;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
-}
-
-.theme-dark .theme-selector button {
- background: rgba(255, 255, 255, 0.1);
-}
-
-.theme-dark .theme-selector button:hover {
- background: rgba(255, 255, 255, 0.2);
-}
-
-/* Scrollbar styling for webkit */
-.dashboard-sidebar::-webkit-scrollbar {
- width: 4px;
-}
-
-.dashboard-sidebar::-webkit-scrollbar-thumb {
- background-color: var(--border-color, #999);
-} \ No newline at end of file
diff --git a/frontend/src/components/FeedList.test.tsx b/frontend/src/components/FeedList.test.tsx
deleted file mode 100644
index d4e72cc..0000000
--- a/frontend/src/components/FeedList.test.tsx
+++ /dev/null
@@ -1,230 +0,0 @@
-import React from 'react';
-import '@testing-library/jest-dom';
-import { render, screen, waitFor, fireEvent } from '@testing-library/react';
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import FeedList from './FeedList';
-
-import { BrowserRouter } from 'react-router-dom';
-
-describe('FeedList Component', () => {
- beforeEach(() => {
- vi.resetAllMocks();
- global.fetch = vi.fn();
- });
-
- it('renders loading state initially', () => {
- vi.mocked(global.fetch).mockImplementation(() => new Promise(() => { }));
- render(
- <BrowserRouter>
- <FeedList
- theme="light"
- setTheme={() => { }}
- setSidebarVisible={() => { }}
- isMobile={false}
- />
- </BrowserRouter>
- );
- expect(screen.getByText(/loading feeds/i)).toBeInTheDocument();
- });
-
- it('renders list of feeds', async () => {
- const mockFeeds = [
- {
- _id: 1,
- title: 'Feed One',
- url: 'http://example.com/rss',
- web_url: 'http://example.com',
- category: 'Tech',
- },
- {
- _id: 2,
- title: 'Feed Two',
- url: 'http://test.com/rss',
- web_url: 'http://test.com',
- category: 'News',
- },
- ];
-
- vi.mocked(global.fetch).mockImplementation((url) => {
- const urlStr = url.toString();
- if (urlStr.includes('/api/feed/')) {
- return Promise.resolve({
- ok: true,
- json: async () => mockFeeds,
- } as Response);
- }
- if (urlStr.includes('/api/tag')) {
- return Promise.resolve({
- ok: true,
- json: async () => [{ title: 'Tech' }],
- } as Response);
- }
- return Promise.reject(new Error(`Unknown URL: ${url}`));
- });
-
- render(
- <BrowserRouter>
- <FeedList
- theme="light"
- setTheme={() => { }}
- setSidebarVisible={() => { }}
- isMobile={false}
- />
- </BrowserRouter>
- );
-
- await waitFor(() => {
- expect(screen.queryByText(/loading feeds/i)).not.toBeInTheDocument();
- });
-
- // Expand feeds
- fireEvent.click(screen.getByText(/feeds/i, { selector: 'h4' }));
-
- await waitFor(() => {
- expect(screen.getByText('Feed One')).toBeInTheDocument();
- expect(screen.getByText('Feed Two')).toBeInTheDocument();
- const techElements = screen.getAllByText('Tech');
- expect(techElements.length).toBeGreaterThan(0);
- });
- });
-
- it('handles fetch error', async () => {
- vi.mocked(global.fetch).mockImplementation(() => Promise.reject(new Error('API Error')));
-
- render(
- <BrowserRouter>
- <FeedList
- theme="light"
- setTheme={() => { }}
- setSidebarVisible={() => { }}
- isMobile={false}
- />
- </BrowserRouter>
- );
-
- await waitFor(() => {
- expect(screen.getByText(/error: api error/i)).toBeInTheDocument();
- });
- });
-
- it('handles empty feed list', async () => {
- vi.mocked(global.fetch).mockImplementation((url) => {
- const urlStr = url.toString();
- if (urlStr.includes('/api/feed/')) {
- return Promise.resolve({
- ok: true,
- json: async () => [],
- } as Response);
- }
- if (urlStr.includes('/api/tag')) {
- return Promise.resolve({
- ok: true,
- json: async () => [],
- } as Response);
- }
- return Promise.reject(new Error(`Unknown URL: ${url}`));
- });
-
- render(
- <BrowserRouter>
- <FeedList
- theme="light"
- setTheme={() => { }}
- setSidebarVisible={() => { }}
- isMobile={false}
- />
- </BrowserRouter>
- );
-
- await waitFor(() => {
- expect(screen.queryByText(/loading feeds/i)).not.toBeInTheDocument();
- });
-
- // Expand feeds
- fireEvent.click(screen.getByText(/feeds/i, { selector: 'h4' }));
-
- await waitFor(() => {
- expect(screen.getByText(/no feeds found/i)).toBeInTheDocument();
- });
- });
-
- it('handles search submission', async () => {
- vi.mocked(global.fetch).mockResolvedValue({ ok: true, json: async () => [] } as Response);
- render(
- <BrowserRouter>
- <FeedList theme="light" setTheme={() => { }} setSidebarVisible={() => { }} isMobile={false} />
- </BrowserRouter>
- );
-
- // Wait for load
- await waitFor(() => {
- expect(screen.queryByText(/loading feeds/i)).not.toBeInTheDocument();
- });
-
- const searchInput = screen.getByPlaceholderText(/search\.\.\./i);
- fireEvent.change(searchInput, { target: { value: 'test search' } });
- fireEvent.submit(searchInput.closest('form')!);
-
- // Should navigate to include search query
- // Since we're using BrowserRouter in test, we can only check if it doesn't crash
- // but we can't easily check 'navigate' unless we mock it.
- });
-
- it('handles logout', async () => {
- vi.mocked(global.fetch).mockResolvedValue({ ok: true, json: async () => [] } as Response);
-
- // Mock window.location
- const originalLocation = window.location;
- const locationMock = new URL('http://localhost/v2/');
-
- delete (window as { location?: Location }).location;
- (window as { location?: unknown }).location = {
- ...originalLocation,
- assign: vi.fn(),
- replace: vi.fn(),
- get href() { return locationMock.href; },
- set href(val: string) { locationMock.href = new URL(val, locationMock.origin).href; }
- };
-
- render(
- <BrowserRouter>
- <FeedList theme="light" setTheme={() => { }} setSidebarVisible={() => { }} isMobile={false} />
- </BrowserRouter>
- );
-
- // Wait for load
- await waitFor(() => {
- expect(screen.queryByText(/loading feeds/i)).not.toBeInTheDocument();
- });
-
- const logoutBtn = screen.getByText(/logout/i);
- fireEvent.click(logoutBtn);
-
- await waitFor(() => {
- expect(global.fetch).toHaveBeenCalledWith('/api/logout', expect.any(Object));
- expect(window.location.href).toContain('/v2/#/login');
- });
- // @ts-expect-error - restoring window.location
- window.location = originalLocation;
- });
-
- it('closes sidebar on mobile link click', async () => {
- vi.mocked(global.fetch).mockResolvedValue({ ok: true, json: async () => [] } as Response);
- const setSidebarVisible = vi.fn();
- render(
- <BrowserRouter>
- <FeedList theme="light" setTheme={() => { }} setSidebarVisible={setSidebarVisible} isMobile={true} />
- </BrowserRouter>
- );
-
- // Wait for load
- await waitFor(() => {
- expect(screen.queryByText(/loading feeds/i)).not.toBeInTheDocument();
- });
-
- const unreadLink = screen.getByText(/unread/i);
- fireEvent.click(unreadLink);
-
- expect(setSidebarVisible).toHaveBeenCalledWith(false);
- });
-});
diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx
deleted file mode 100644
index ce83333..0000000
--- a/frontend/src/components/FeedList.tsx
+++ /dev/null
@@ -1,279 +0,0 @@
-import { useEffect, useState } from 'react';
-import { Link, useNavigate, useSearchParams, useLocation, useMatch } from 'react-router-dom';
-import type { Feed, Category } from '../types';
-import './FeedList.css';
-import './FeedListVariants.css';
-import { apiFetch } from '../utils';
-
-export default function FeedList({
- theme,
- setTheme,
- setSidebarVisible,
- isMobile,
-}: {
- theme: string;
- setTheme: (t: string) => void;
- setSidebarVisible: (visible: boolean) => void;
- isMobile: boolean;
-}) {
- const [feeds, setFeeds] = useState<Feed[]>([]);
- const [tags, setTags] = useState<Category[]>([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState('');
- const [feedsExpanded, setFeedsExpanded] = useState(false);
- const [tagsExpanded, setTagsExpanded] = useState(true);
- const [searchQuery, setSearchQuery] = useState('');
- const navigate = useNavigate();
- const [searchParams] = useSearchParams();
- const location = useLocation();
- const feedMatch = useMatch('/feed/:feedId');
- const tagMatch = useMatch('/tag/:tagName');
- const isRoot = useMatch('/');
- const isStreamPage = !!(isRoot || feedMatch || tagMatch);
-
- const feedId = feedMatch?.params.feedId;
- const tagName = tagMatch?.params.tagName;
-
- const sidebarVariant = searchParams.get('sidebar') || localStorage.getItem('neko-sidebar-variant') || 'glass';
-
- useEffect(() => {
- const variant = searchParams.get('sidebar');
- if (variant) {
- localStorage.setItem('neko-sidebar-variant', variant);
- }
- }, [searchParams]);
-
- const currentFilter = searchParams.get('filter') || (isStreamPage ? 'unread' : '');
-
- const getFilterLink = (filter: string) => {
- const baseStreamPath = isStreamPage ? location.pathname : '/';
- const params = new URLSearchParams(searchParams);
- params.set('filter', filter);
- return `${baseStreamPath}?${params.toString()}`;
- };
-
- const getNavPath = (path: string) => {
- const params = new URLSearchParams(searchParams);
- if (!params.has('filter') && currentFilter) {
- params.set('filter', currentFilter);
- }
- const qs = params.toString();
- return `${path}${qs ? '?' + qs : ''}`;
- };
-
- const handleSearch = (e: React.FormEvent) => {
- e.preventDefault();
- if (searchQuery.trim()) {
- const params = new URLSearchParams(searchParams);
- params.set('q', searchQuery.trim());
- if (currentFilter) params.set('filter', currentFilter);
- navigate(`/?${params.toString()}`);
- }
- };
-
- const toggleFeeds = () => {
- setFeedsExpanded(!feedsExpanded);
- };
-
- const toggleTags = () => {
- setTagsExpanded(!tagsExpanded);
- };
-
- const handleLinkClick = () => {
- if (isMobile) {
- setSidebarVisible(false);
- }
- };
-
- useEffect(() => {
- Promise.all([
- apiFetch('/api/feed/').then((res) => {
- if (!res.ok) throw new Error('Failed to fetch feeds');
- return res.json() as Promise<Feed[]>;
- }),
- apiFetch('/api/tag').then((res) => {
- if (!res.ok) throw new Error('Failed to fetch tags');
- return res.json() as Promise<Category[]>;
- }),
- ])
- .then(([feedsData, tagsData]) => {
- setFeeds(feedsData);
- setTags(tagsData);
- setLoading(false);
- })
- .catch((err) => {
- setError(err.message);
- setLoading(false);
- });
- }, []);
-
- if (loading) return <div className="feed-list-loading">Loading feeds...</div>;
- if (error) return <div className="feed-list-error">Error: {error}</div>;
-
- const handleLogout = () => {
- apiFetch('/api/logout', { method: 'POST' }).then(() => (window.location.href = '/v2/#/login'));
- };
-
- return (
- <div className={`feed-list variant-${sidebarVariant}`}>
- <h1 className="logo" onClick={() => setSidebarVisible(false)}>
- 🐱
- </h1>
-
- <div className="search-section">
- <form onSubmit={handleSearch} className="search-form">
- <input
- type="search"
- placeholder="search..."
- value={searchQuery}
- onChange={(e) => setSearchQuery(e.target.value)}
- className="search-input"
- />
- </form>
- </div>
-
- <div className="filter-section">
- <ul className="filter-list">
- <li className="unread_filter">
- <Link
- to={getFilterLink('unread')}
- className={currentFilter === 'unread' ? 'active' : ''}
- onClick={handleLinkClick}
- >
- unread
- </Link>
- </li>
- <li className="all_filter">
- <Link
- to={getFilterLink('all')}
- className={currentFilter === 'all' ? 'active' : ''}
- onClick={handleLinkClick}
- >
- all
- </Link>
- </li>
- <li className="starred_filter">
- <Link
- to={getFilterLink('starred')}
- className={currentFilter === 'starred' ? 'active' : ''}
- onClick={handleLinkClick}
- >
- starred
- </Link>
- </li>
- </ul>
- </div>
-
- <div className="tag-section">
- <h4 onClick={toggleTags} className="section-header">
- <span className={`caret ${tagsExpanded ? 'expanded' : ''}`}>▶</span> Tags
- </h4>
- {tagsExpanded && (
- <ul className="tag-list-items">
- {tags.map((tag) => (
- <li key={tag.title} className="tag-item">
- <Link
- to={getNavPath(`/tag/${encodeURIComponent(tag.title)}`)}
- className={`tag-link ${tagName === tag.title ? 'active' : ''}`}
- onClick={handleLinkClick}
- >
- {tag.title}
- </Link>
- </li>
- ))}
- </ul>
- )}
- </div>
-
- <div className="feed-section">
- <h4 onClick={toggleFeeds} className="section-header">
- <span className={`caret ${feedsExpanded ? 'expanded' : ''}`}>▶</span> Feeds
- </h4>
- {feedsExpanded &&
- (feeds.length === 0 ? (
- <p>No feeds found.</p>
- ) : (
- <ul className="feed-list-items">
- {feeds.map((feed) => {
- const isSelected = feedId?.split(',').includes(String(feed._id));
-
- const toggleFeed = (e: React.MouseEvent) => {
- e.preventDefault();
- e.stopPropagation();
-
- const selectedIds = feedId ? feedId.split(',') : [];
- let newIds;
- if (isSelected) {
- newIds = selectedIds.filter(id => id !== String(feed._id));
- } else {
- newIds = [...selectedIds, String(feed._id)];
- }
-
- if (newIds.length === 0) {
- navigate(getNavPath('/'));
- } else {
- navigate(getNavPath(`/feed/${newIds.join(',')}`));
- }
- };
-
- return (
- <li key={feed._id} className="sidebar-feed-item">
- <div className="feed-item-row">
- <input
- type="checkbox"
- checked={!!isSelected}
- onChange={() => { }} // Controlled by div click for better hit area
- onClick={toggleFeed}
- className="feed-checkbox"
- />
- <Link
- to={getNavPath(`/feed/${feed._id}`)}
- className={`feed-title ${isSelected ? 'active' : ''}`}
- onClick={handleLinkClick}
- >
- {feed.title || feed.url}
- </Link>
- </div>
- </li>
- );
- })}
- </ul>
- ))}
- </div>
-
- <div className="nav-section">
- <ul className="nav-list">
- <li>
- <Link to="/settings" className="nav-link" onClick={handleLinkClick}>
- settings
- </Link>
- </li>
- <li>
- <button onClick={handleLogout} className="logout-link">
- logout
- </button>
- </li>
- </ul>
- </div>
-
- <div className="theme-section">
- <div className="theme-selector">
- <button
- onClick={() => setTheme('light')}
- className={theme === 'light' ? 'active' : ''}
- title="Light Theme"
- >
- ☀️
- </button>
- <button
- onClick={() => setTheme('dark')}
- className={theme === 'dark' ? 'active' : ''}
- title="Dark Theme"
- >
- 🌙
- </button>
- </div>
- </div>
- </div>
- );
-}
diff --git a/frontend/src/components/FeedListVariants.css b/frontend/src/components/FeedListVariants.css
deleted file mode 100644
index e97ea95..0000000
--- a/frontend/src/components/FeedListVariants.css
+++ /dev/null
@@ -1,342 +0,0 @@
-/* Glass Variant */
-.feed-list.variant-glass {
- background: rgba(255, 255, 255, 0.05);
- /* Very subtle tint */
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
- border-right: 1px solid rgba(255, 255, 255, 0.1);
- padding: 1.5rem;
- font-family: system-ui, -apple-system, sans-serif;
- /* Modern sans */
- color: var(--text-color);
-}
-
-.feed-list.variant-glass .logo {
- font-size: 1.5rem;
- background: transparent !important;
- /* Override sticky bg */
- margin-bottom: 2rem;
- opacity: 0.8;
-}
-
-.feed-list.variant-glass .section-header {
- font-size: 0.75rem;
- text-transform: uppercase;
- letter-spacing: 0.1em;
- color: var(--text-color);
- opacity: 0.5;
- margin-top: 2rem;
- font-weight: 600;
-}
-
-.feed-list.variant-glass a,
-.feed-list.variant-glass .feed-title,
-.feed-list.variant-glass .tag-link {
- padding: 0.4rem 0.8rem;
- margin: 0.2rem 0;
- border-radius: 8px;
- transition: all 0.2s ease;
- font-weight: 500;
- text-decoration: none !important;
- /* No underlines in glass */
- color: var(--text-color);
- opacity: 0.8;
- border: none;
- /* No default borders */
-}
-
-.feed-list.variant-glass a:hover,
-.feed-list.variant-glass .feed-title:hover,
-.feed-list.variant-glass .tag-link:hover {
- background: rgba(255, 255, 255, 0.1);
- opacity: 1;
- transform: translateX(4px);
- color: var(--text-color);
-}
-
-.feed-list.variant-glass a.active,
-.feed-list.variant-glass .feed-title.active,
-.feed-list.variant-glass .tag-link.active {
- background: rgba(255, 255, 255, 0.25);
- color: var(--text-color);
- font-weight: 700;
- opacity: 1;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
- border: 1px solid rgba(255, 255, 255, 0.2);
-}
-
-.feed-list.variant-glass .search-input {
- border-radius: 20px;
- background: rgba(0, 0, 0, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.1);
- color: var(--text-color);
- padding: 0.5rem 1rem;
-}
-
-.feed-list.variant-glass .nav-section {
- border-top: 1px solid rgba(255, 255, 255, 0.1);
- margin-top: 2rem;
- padding-top: 1.5rem;
-}
-
-.feed-list.variant-glass .nav-link,
-.feed-list.variant-glass .logout-link {
- opacity: 0.6;
- padding: 0.5rem 0.8rem;
- border-radius: 8px;
-}
-
-.feed-list.variant-glass .nav-link:hover,
-.feed-list.variant-glass .logout-link:hover {
- background: rgba(255, 255, 255, 0.05);
- opacity: 1;
- text-decoration: none;
-}
-
-.feed-list.variant-glass .theme-selector button {
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 12px;
-}
-
-.feed-list.variant-glass .theme-selector button.active {
- background: rgba(255, 255, 255, 0.2);
- border-color: rgba(255, 255, 255, 0.3);
-}
-
-
-/* Nano Banana Variant (Playful/Pop) */
-.feed-list.variant-banana {
- background: #fdfdfd;
- padding: 1rem;
- font-family: 'Poppins', 'Verdana', sans-serif;
- border-right: none;
- box-shadow: 4px 0 24px rgba(0, 0, 0, 0.04);
-}
-
-.theme-dark .feed-list.variant-banana {
- background: #111;
-}
-
-.feed-list.variant-banana .logo {
- font-size: 2.5rem;
- text-shadow: 2px 2px 0px #FFD700;
- /* Banana yellow shadow */
- background: transparent;
- transform: rotate(-3deg);
- display: inline-block;
- margin-bottom: 2rem;
-}
-
-.feed-list.variant-banana .section-header {
- background: #FFD700;
- color: #000;
- display: inline-block;
- padding: 0.2rem 0.5rem;
- transform: skew(-10deg);
- font-size: 0.8rem;
- font-weight: 800;
- margin-bottom: 1rem;
- border-radius: 4px;
-}
-
-.feed-list.variant-banana .search-input {
- border: 2px solid #000;
- border-radius: 8px;
- box-shadow: 2px 2px 0px #000;
- transition: all 0.2s;
-}
-
-.feed-list.variant-banana .search-input:focus {
- transform: translate(1px, 1px);
- box-shadow: 1px 1px 0px #000;
- outline: none;
-}
-
-.theme-dark .feed-list.variant-banana .search-input {
- border-color: #fff;
- box-shadow: 2px 2px 0px #fff;
- background: #222;
- color: #fff;
-}
-
-.feed-list.variant-banana a,
-.feed-list.variant-banana .feed-title,
-.feed-list.variant-banana .tag-link {
- border: 1px solid transparent;
- padding: 0.5rem;
- border-radius: 8px;
- transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
- font-weight: 600;
- text-decoration: none !important;
- color: var(--text-color);
-}
-
-.feed-list.variant-banana a:hover,
-.feed-list.variant-banana .feed-title:hover,
-.feed-list.variant-banana .tag-link:hover {
- transform: scale(1.05) rotate(1deg);
- background: #fff9c4;
- /* Light yellow */
- color: #000;
- box-shadow: 0 4px 12px rgba(255, 215, 0, 0.3);
-}
-
-.theme-dark .feed-list.variant-banana a:hover,
-.theme-dark .feed-list.variant-banana .feed-title:hover,
-.theme-dark .feed-list.variant-banana .tag-link:hover {
- background: #333;
- color: #FFD700;
-}
-
-
-.feed-list.variant-banana a.active,
-.feed-list.variant-banana .feed-title.active,
-.feed-list.variant-banana .tag-link.active {
- background: #FFD700;
- color: #000 !important;
- transform: scale(1.02);
- box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.1);
- border: 2px solid #000;
-}
-
-.feed-list.variant-banana .nav-section {
- border-top: 2px dashed #FFD700;
- margin-top: 2rem;
- padding-top: 1rem;
-}
-
-.feed-list.variant-banana .theme-selector button {
- border: 2px solid #000;
- box-shadow: 2px 2px 0px #000;
- border-radius: 4px;
-}
-
-.feed-list.variant-banana .theme-selector button.active {
- background: #FFD700;
- transform: translate(1px, 1px);
- box-shadow: 1px 1px 0px #000;
-}
-
-
-/* Type Variant (Swiss/Bold) */
-.feed-list.variant-type {
- background: var(--bg-color);
- padding: 2rem 1rem;
- font-family: 'Helvetica Neue', 'Arial', sans-serif;
- border-right: 4px solid var(--text-color);
-}
-
-.feed-list.variant-type .logo {
- font-size: 3rem;
- letter-spacing: -2px;
- font-weight: 900;
- background: transparent;
- line-height: 0.8;
- margin-bottom: 3rem;
- color: var(--text-color);
-}
-
-.feed-list.variant-type .section-header {
- font-size: 1.2rem;
- font-weight: 900;
- border-bottom: 2px solid var(--text-color);
- padding-bottom: 0.5rem;
- margin-top: 3rem;
- margin-bottom: 1rem;
- letter-spacing: -0.5px;
- color: var(--text-color);
-}
-
-.feed-list.variant-type a,
-.feed-list.variant-type .feed-title,
-.feed-list.variant-type .tag-link {
- font-size: 1rem;
- font-weight: 700;
- text-decoration: none !important;
- border-left: 0px solid var(--text-color);
- padding-left: 0;
- transition: padding-left 0.2s, border-left-width 0.2s;
- opacity: 0.6;
- color: var(--text-color);
- padding: 0.5rem 0;
- display: block;
-}
-
-.feed-list.variant-type a:hover,
-.feed-list.variant-type .feed-title:hover,
-.feed-list.variant-type .tag-link:hover {
- padding-left: 1rem;
- border-left: 4px solid var(--text-color);
- opacity: 1;
- color: var(--text-color);
-}
-
-.feed-list.variant-type a.active,
-.feed-list.variant-type .feed-title.active,
-.feed-list.variant-type .tag-link.active {
- padding-left: 1rem;
- border-left: 8px solid var(--text-color);
- opacity: 1;
- color: var(--text-color);
-}
-
-.feed-list.variant-type .search-input {
- border: none;
- border-bottom: 2px solid var(--text-color);
- background: transparent;
- border-radius: 0;
- padding: 1rem 0;
- font-weight: bold;
- font-size: 1.2rem;
-}
-
-.feed-list.variant-type .search-input:focus {
- outline: none;
- border-bottom-width: 4px;
-}
-.feed-list.variant-type .nav-section {
- border-top: 4px solid var(--text-color);
- margin-top: 4rem;
- padding-top: 1rem;
-}
-
-.feed-list.variant-type .nav-link,
-.feed-list.variant-type .logout-link {
- font-size: 1.2rem;
- font-weight: 900;
-}
-
-.feed-list.variant-type .theme-selector button {
- border-radius: 0;
- border: 2px solid var(--text-color);
- background: transparent;
-}
-
-.feed-list.variant-type .theme-selector button.active {
- background: var(--text-color);
- color: var(--bg-color);
-}
-
-.feed-list.variant-type .nav-section {
- border-top: 4px solid var(--text-color);
- margin-top: 4rem;
- padding-top: 1rem;
-}
-
-.feed-list.variant-type .nav-link,
-.feed-list.variant-type .logout-link {
- font-size: 1.2rem;
- font-weight: 900;
-}
-
-.feed-list.variant-type .theme-selector button {
- border-radius: 0;
- border: 2px solid var(--text-color);
- background: transparent;
-}
-
-.feed-list.variant-type .theme-selector button.active {
- background: var(--text-color);
- color: var(--bg-color);
-}
diff --git a/frontend/src/components/Login.css b/frontend/src/components/Login.css
deleted file mode 100644
index 6f40731..0000000
--- a/frontend/src/components/Login.css
+++ /dev/null
@@ -1,63 +0,0 @@
-.login-container {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100vh;
- background-color: #f5f5f5;
-}
-
-.login-form {
- background: white;
- padding: 2rem;
- border-radius: 8px;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
- width: 100%;
- max-width: 400px;
-}
-
-.login-form h1 {
- margin-bottom: 2rem;
- text-align: center;
- color: #333;
-}
-
-.form-group {
- margin-bottom: 1.5rem;
-}
-
-.form-group label {
- display: block;
- margin-bottom: 0.5rem;
- font-weight: bold;
- color: #555;
-}
-
-.form-group input {
- width: 100%;
- padding: 0.75rem;
- border: 1px solid #ddd;
- border-radius: 4px;
- font-size: 1rem;
-}
-
-.error-message {
- color: #dc3545;
- margin-bottom: 1rem;
- text-align: center;
-}
-
-button[type='submit'] {
- width: 100%;
- padding: 0.75rem;
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 4px;
- font-size: 1rem;
- cursor: pointer;
- transition: background-color 0.2s;
-}
-
-button[type='submit']:hover {
- background-color: #0056b3;
-}
diff --git a/frontend/src/components/Login.test.tsx b/frontend/src/components/Login.test.tsx
deleted file mode 100644
index 47f37e3..0000000
--- a/frontend/src/components/Login.test.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import React from 'react';
-import '@testing-library/jest-dom';
-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
-import { BrowserRouter } from 'react-router-dom';
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import Login from './Login';
-
-// Mock fetch
-global.fetch = vi.fn();
-
-const renderLogin = () => {
- render(
- <BrowserRouter>
- <Login />
- </BrowserRouter>
- );
-};
-
-describe('Login Component', () => {
- beforeEach(() => {
- vi.resetAllMocks();
- });
-
- it('renders login form', () => {
- renderLogin();
- expect(screen.getByLabelText(/username/i)).toBeInTheDocument();
- expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
- expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument();
- });
-
- it('handles successful login', async () => {
- vi.mocked(global.fetch).mockResolvedValueOnce({
- ok: true,
- } as Response);
-
- renderLogin();
-
- fireEvent.change(screen.getByLabelText(/username/i), { target: { value: 'testuser' } });
- fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'secret' } });
- fireEvent.click(screen.getByRole('button', { name: /login/i }));
-
- await waitFor(() => {
- expect(global.fetch).toHaveBeenCalledWith(
- '/api/login',
- expect.objectContaining({
- method: 'POST',
- body: expect.any(URLSearchParams),
- })
- );
- });
-
- // Check if params contained username and password
- const callArgs = vi.mocked(global.fetch).mock.calls[0][1];
- const body = callArgs?.body as URLSearchParams;
- expect(body.get('username')).toBe('testuser');
- expect(body.get('password')).toBe('secret');
-
- // Navigation assertion is tricky without mocking useNavigate,
- // but if no error is shown, we assume success path was taken
- expect(screen.queryByText(/login failed/i)).not.toBeInTheDocument();
- });
-
- it('handles failed login', async () => {
- vi.mocked(global.fetch).mockResolvedValueOnce({
- ok: false,
- json: async () => ({ message: 'Bad credentials' }),
- } as Response);
-
- renderLogin();
-
- fireEvent.change(screen.getByLabelText(/username/i), { target: { value: 'testuser' } });
- fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'wrong' } });
- fireEvent.click(screen.getByRole('button', { name: /login/i }));
-
- await waitFor(() => {
- expect(screen.getByText(/bad credentials/i)).toBeInTheDocument();
- });
- });
-
- it('handles network error', async () => {
- vi.mocked(global.fetch).mockRejectedValueOnce(new Error('Network error'));
-
- renderLogin();
-
- fireEvent.change(screen.getByLabelText(/username/i), { target: { value: 'testuser' } });
- fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'secret' } });
- fireEvent.click(screen.getByRole('button', { name: /login/i }));
-
- await waitFor(() => {
- expect(screen.getByText(/network error/i)).toBeInTheDocument();
- });
- });
-});
diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx
deleted file mode 100644
index 87694cb..0000000
--- a/frontend/src/components/Login.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { useState, type FormEvent } from 'react';
-import { useNavigate } from 'react-router-dom';
-import './Login.css';
-
-import { apiFetch } from '../utils';
-
-export default function Login() {
- const [username, setUsername] = useState('neko');
- const [password, setPassword] = useState('');
- const [error, setError] = useState('');
- const navigate = useNavigate();
-
- const handleSubmit = async (e: FormEvent) => {
- e.preventDefault();
- setError('');
-
- try {
- // Use URLSearchParams to send as form-urlencoded, matching backend expectation
- const params = new URLSearchParams();
- params.append('username', username);
- params.append('password', password);
-
- const res = await apiFetch('/api/login', {
- method: 'POST',
- body: params,
- });
-
- if (res.ok) {
- navigate('/');
- } else {
- const data = await res.json();
- setError(data.message || 'Login failed');
- }
- } catch (_err) {
- setError('Network error');
- }
- };
-
- return (
- <div className="login-container">
- <form onSubmit={handleSubmit} className="login-form">
- <h1>neko rss mode</h1>
- <div className="form-group">
- <label htmlFor="username">username</label>
- <input
- id="username"
- type="text"
- value={username}
- onChange={(e) => setUsername(e.target.value)}
- />
- </div>
- <div className="form-group">
- <label htmlFor="password">password</label>
- <input
- id="password"
- type="password"
- value={password}
- onChange={(e) => setPassword(e.target.value)}
- autoFocus
- />
- </div>
- {error && <div className="error-message">{error}</div>}
- <button type="submit">login</button>
- </form>
- </div>
- );
-}
diff --git a/frontend/src/components/Settings.css b/frontend/src/components/Settings.css
deleted file mode 100644
index ae43be4..0000000
--- a/frontend/src/components/Settings.css
+++ /dev/null
@@ -1,240 +0,0 @@
-.settings-page.variant-glass {
- padding: 2.5rem;
- max-width: 800px;
- margin: 0 auto;
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
- border-radius: 24px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- font-family: system-ui, -apple-system, sans-serif;
- color: var(--text-color);
- margin-top: 2rem;
- margin-bottom: 2rem;
-}
-
-.settings-page.variant-glass h2,
-.settings-page.variant-glass h3 {
- font-weight: 700;
- letter-spacing: -0.02em;
- color: var(--text-color);
- opacity: 0.9;
-}
-
-.add-feed-section,
-.appearance-section,
-.import-section,
-.export-section,
-.feed-list-section {
- background: rgba(255, 255, 255, 0.03);
- padding: 1.5rem;
- border-radius: 16px;
- margin-bottom: 2rem;
- border: 1px solid rgba(255, 255, 255, 0.05);
- transition: all 0.3s ease;
-}
-
-.add-feed-section:hover,
-.appearance-section:hover,
-.import-section:hover,
-.export-section:hover,
-.feed-list-section:hover {
- background: rgba(255, 255, 255, 0.06);
- border-color: rgba(255, 255, 255, 0.1);
-}
-
-.font-selector {
- display: flex;
- align-items: center;
- gap: 1rem;
-}
-
-.font-select {
- padding: 0.6rem 1rem;
- border: 1px solid rgba(255, 255, 255, 0.1);
- background: rgba(0, 0, 0, 0.1);
- color: var(--text-color);
- border-radius: 20px;
- font-size: 1rem;
- min-width: 200px;
- cursor: pointer;
- outline: none;
- transition: border-color 0.2s;
-}
-
-.font-select:focus {
- border-color: rgba(255, 255, 255, 0.3);
-}
-
-.add-feed-form {
- display: flex;
- gap: 1rem;
-}
-
-.feed-input {
- flex: 1;
- padding: 0.6rem 1.2rem;
- border: 1px solid rgba(255, 255, 255, 0.1);
- background: rgba(0, 0, 0, 0.1);
- color: var(--text-color);
- border-radius: 20px;
- font-size: 1rem;
- outline: none;
- transition: border-color 0.2s;
-}
-
-.feed-input:focus {
- border-color: rgba(255, 255, 255, 0.3);
-}
-
-.error-message {
- color: #ff5252;
- margin-top: 1rem;
- font-weight: 600;
-}
-
-.settings-feed-list {
- list-style: none;
- padding: 0;
- border: 1px solid rgba(255, 255, 255, 0.05);
- border-radius: 12px;
- overflow: hidden;
-}
-
-.settings-feed-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 1.2rem;
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
- transition: background 0.2s;
-}
-
-.settings-feed-item:hover {
- background: rgba(255, 255, 255, 0.02);
-}
-
-.settings-feed-item:last-child {
- border-bottom: none;
-}
-
-.feed-info {
- display: flex;
- flex-direction: column;
- gap: 0.2rem;
-}
-
-.feed-title {
- font-weight: 600;
- font-size: 1.05rem;
- opacity: 0.9;
-}
-
-.feed-url {
- color: var(--text-color);
- opacity: 0.5;
- font-size: 0.85rem;
-}
-
-.delete-btn {
- background: rgba(255, 82, 82, 0.15);
- color: #ff8a80;
- border: 1px solid rgba(255, 82, 82, 0.2);
- padding: 0.5rem 1rem;
- border-radius: 12px;
- cursor: pointer;
- font-weight: 600;
- transition: all 0.2s;
-}
-
-.delete-btn:hover:not(:disabled) {
- background: rgba(255, 82, 82, 0.3);
- color: #fff;
- border-color: rgba(255, 82, 82, 0.4);
- transform: scale(1.05);
-}
-
-.import-export-section {
- display: flex;
- gap: 2rem;
-}
-
-@media (max-width: 768px) {
- .settings-page.variant-glass {
- padding: 1.5rem;
- margin-top: 1rem;
- }
-
- .add-feed-form {
- flex-direction: column;
- }
-
- .import-export-section {
- flex-direction: column;
- gap: 1rem;
- }
-
- .settings-feed-item {
- flex-direction: column;
- align-items: flex-start;
- gap: 1rem;
- }
-}
-
-.import-form {
- display: flex;
- flex-direction: column;
- gap: 1.2rem;
-}
-
-.file-input {
- font-size: 0.9rem;
- max-width: 100%;
- color: var(--text-color);
- opacity: 0.8;
-}
-
-.export-buttons {
- display: flex;
- gap: 0.8rem;
- flex-wrap: wrap;
-}
-
-.export-btn {
- display: inline-block;
- padding: 0.6rem 1.2rem;
- background: rgba(255, 255, 255, 0.05);
- color: var(--text-color);
- text-decoration: none;
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 12px;
- font-weight: 600;
- transition: all 0.2s;
-}
-
-.export-btn:hover {
- background: rgba(255, 255, 255, 0.1);
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
-}
-
-button:not(.delete-btn) {
- cursor: pointer;
- padding: 0.6rem 1.2rem;
- border-radius: 12px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- background: rgba(255, 255, 255, 0.1);
- color: var(--text-color);
- font-weight: 600;
- transition: all 0.2s;
-}
-
-button:not(.delete-btn):hover:not(:disabled) {
- background: rgba(255, 255, 255, 0.2);
- transform: scale(1.02);
-}
-
-button:disabled {
- opacity: 0.4;
- cursor: not-allowed;
-} \ No newline at end of file
diff --git a/frontend/src/components/Settings.test.tsx b/frontend/src/components/Settings.test.tsx
deleted file mode 100644
index 5b0518c..0000000
--- a/frontend/src/components/Settings.test.tsx
+++ /dev/null
@@ -1,207 +0,0 @@
-import React from 'react';
-import '@testing-library/jest-dom';
-import { render, screen, waitFor, fireEvent } from '@testing-library/react';
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import Settings from './Settings';
-
-describe('Settings Component', () => {
- beforeEach(() => {
- vi.resetAllMocks();
- global.fetch = vi.fn();
- // Mock confirm
- global.confirm = vi.fn(() => true);
- });
-
- it('renders feed list', async () => {
- const mockFeeds = [
- { _id: 1, title: 'Tech News', url: 'http://tech.com/rss', category: 'tech' },
- { _id: 2, title: 'Gaming', url: 'http://gaming.com/rss', category: 'gaming' },
- ];
-
- vi.mocked(global.fetch).mockResolvedValueOnce({
- ok: true,
- json: async () => mockFeeds,
- } as Response);
-
- render(<Settings />);
-
- await waitFor(() => {
- expect(screen.getByText('Tech News')).toBeInTheDocument();
- expect(screen.getByText('http://tech.com/rss')).toBeInTheDocument();
- expect(screen.getByText('Gaming')).toBeInTheDocument();
- });
- });
-
- it('adds a new feed', async () => {
- vi.mocked(global.fetch)
- .mockResolvedValueOnce({ ok: true, json: async () => [] } as Response) // Initial load
- .mockResolvedValueOnce({ ok: true, json: async () => ({}) } as Response) // Add feed
- .mockResolvedValueOnce({
- ok: true,
- json: async () => [{ _id: 3, title: 'New Feed', url: 'http://new.com/rss' }],
- } as Response); // Refresh load
-
- render(<Settings />);
-
- // Wait for initial load to finish
- await waitFor(() => {
- expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
- });
-
- const input = screen.getByPlaceholderText('https://example.com/feed.xml');
- const button = screen.getByText('Add Feed');
-
- fireEvent.change(input, { target: { value: 'http://new.com/rss' } });
- fireEvent.click(button);
-
- await waitFor(() => {
- expect(global.fetch).toHaveBeenCalledWith(
- '/api/feed/',
- expect.objectContaining({
- method: 'POST',
- body: JSON.stringify({ url: 'http://new.com/rss' }),
- })
- );
- });
-
- // Wait for refresh
- await waitFor(() => {
- expect(screen.getByText('New Feed')).toBeInTheDocument();
- });
- });
-
- it('deletes a feed', async () => {
- const mockFeeds = [
- { _id: 1, title: 'Tech News', url: 'http://tech.com/rss', category: 'tech' },
- ];
-
- vi.mocked(global.fetch)
- .mockResolvedValueOnce({ ok: true, json: async () => mockFeeds } as Response) // Initial load
- .mockResolvedValueOnce({ ok: true } as Response); // Delete
-
- render(<Settings />);
-
- await waitFor(() => {
- expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
- expect(screen.getByText('Tech News')).toBeInTheDocument();
- });
-
- const deleteBtn = screen.getByTitle('Delete Feed');
- fireEvent.click(deleteBtn);
-
- await waitFor(() => {
- expect(global.confirm).toHaveBeenCalled();
- expect(global.fetch).toHaveBeenCalledWith(
- '/api/feed/1',
- expect.objectContaining({ method: 'DELETE' })
- );
- expect(screen.queryByText('Tech News')).not.toBeInTheDocument();
- });
- });
-
- it('imports an OPML file', async () => {
- vi.mocked(global.fetch)
- .mockResolvedValueOnce({ ok: true, json: async () => [] } as Response) // Initial load
- .mockResolvedValueOnce({ ok: true, json: async () => ({ status: 'ok' }) } as Response) // Import
- .mockResolvedValueOnce({
- ok: true,
- json: async () => [{ _id: 1, title: 'Imported Feed', url: 'http://imported.com/rss' }],
- } as Response); // Refresh load
-
- render(<Settings />);
-
- const file = new File(['<opml>...</opml>'], 'feeds.opml', { type: 'text/xml' });
- const fileInput = screen.getByLabelText(/import feeds/i, { selector: 'input[type="file"]' });
- const importButton = screen.getByText('Import');
-
- fireEvent.change(fileInput, { target: { files: [file] } });
- await waitFor(() => {
- expect(importButton).not.toBeDisabled();
- });
-
- fireEvent.click(importButton);
-
- await waitFor(() => {
- expect(global.fetch).toHaveBeenCalledWith(
- '/api/import',
- expect.objectContaining({
- method: 'POST',
- body: expect.any(FormData),
- })
- );
- });
-
- // Check if refresh happens
- await waitFor(() => {
- expect(screen.getByText('Imported Feed')).toBeInTheDocument();
- });
- });
-
- it('triggers a crawl', async () => {
- vi.mocked(global.fetch)
- .mockResolvedValueOnce({ ok: true, json: async () => [] } as Response) // Initial load
- .mockResolvedValueOnce({ ok: true, json: async () => ({ message: 'crawl started' }) } as Response); // Crawl
-
- // Mock alert
- const alertMock = vi.spyOn(window, 'alert').mockImplementation(() => { });
-
- render(<Settings />);
-
- // Wait for load
- await waitFor(() => {
- expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
- });
-
- const crawlBtn = screen.getByText(/crawl all feeds now/i);
- fireEvent.click(crawlBtn);
-
- await waitFor(() => {
- expect(global.fetch).toHaveBeenCalledWith(
- '/api/crawl',
- expect.objectContaining({ method: 'POST' })
- );
- expect(alertMock).toHaveBeenCalledWith('Crawl started!');
- });
- alertMock.mockRestore();
- });
-
- it('handles API errors', async () => {
- vi.mocked(global.fetch)
- .mockResolvedValueOnce({ ok: true, json: async () => [] } as Response) // Initial load load
- .mockResolvedValueOnce({ ok: false, json: async () => ({}) } as Response); // Add feed error
-
- render(<Settings />);
-
- // Wait for load
- await waitFor(() => {
- expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
- });
-
- const input = screen.getByPlaceholderText('https://example.com/feed.xml');
- const button = screen.getByText('Add Feed');
-
- fireEvent.change(input, { target: { value: 'http://fail.com/rss' } });
- fireEvent.click(button);
-
- await waitFor(() => {
- expect(screen.getByText(/failed to add feed/i)).toBeInTheDocument();
- });
- });
-
- it('handles font theme change', async () => {
- const setFontTheme = vi.fn();
- vi.mocked(global.fetch).mockResolvedValueOnce({ ok: true, json: async () => [] } as Response);
-
- render(<Settings fontTheme="default" setFontTheme={setFontTheme} />);
-
- // Wait for load
- await waitFor(() => {
- expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
- });
-
- const select = screen.getByLabelText(/font theme/i);
- fireEvent.change(select, { target: { value: 'serif' } });
-
- expect(setFontTheme).toHaveBeenCalledWith('serif');
- });
-});
diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx
deleted file mode 100644
index 3dab77f..0000000
--- a/frontend/src/components/Settings.tsx
+++ /dev/null
@@ -1,236 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import type { Feed } from '../types';
-import './Settings.css';
-import { apiFetch } from '../utils';
-
-interface SettingsProps {
- fontTheme?: string;
- setFontTheme?: (t: string) => void;
-}
-
-export default function Settings({ fontTheme, setFontTheme }: SettingsProps) {
- const [feeds, setFeeds] = useState<Feed[]>([]);
- /* ... existing state ... */
- const [newFeedUrl, setNewFeedUrl] = useState('');
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState<string | null>(null);
-
- const [importFile, setImportFile] = useState<File | null>(null);
-
- /* ... existing fetchFeeds ... */
- const fetchFeeds = React.useCallback(() => {
- setLoading(true);
- apiFetch('/api/feed/')
- .then((res) => {
- if (!res.ok) throw new Error('Failed to fetch feeds');
- return res.json();
- })
- .then((data) => {
- setFeeds(data);
- setLoading(false);
- })
- .catch((err) => {
- setError(err.message);
- setLoading(false);
- });
- }, []);
-
- useEffect(() => {
- // eslint-disable-next-line
- fetchFeeds();
- }, [fetchFeeds]);
-
- /* ... existing handlers ... */
- const handleAddFeed = (e: React.FormEvent) => {
- e.preventDefault();
- if (!newFeedUrl) return;
-
- setLoading(true);
- apiFetch('/api/feed/', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ url: newFeedUrl }),
- })
- .then((res) => {
- if (!res.ok) throw new Error('Failed to add feed');
- return res.json();
- })
- .then(() => {
- setNewFeedUrl('');
- fetchFeeds();
- })
- .catch((err) => {
- setError(err.message);
- setLoading(false);
- });
- };
-
- const handleDeleteFeed = (id: number) => {
- if (!globalThis.confirm('Are you sure you want to delete this feed?')) return;
-
- setLoading(true);
- apiFetch(`/api/feed/${id}`, {
- method: 'DELETE',
- })
- .then((res) => {
- if (!res.ok) throw new Error('Failed to delete feed');
- setFeeds(feeds.filter((f) => f._id !== id));
- setLoading(false);
- })
- .catch((err) => {
- setError(err.message);
- setLoading(false);
- });
- };
-
- const handleImport = (e: React.FormEvent) => {
- e.preventDefault();
- if (!importFile) return;
-
- setLoading(true);
- const formData = new FormData();
- formData.append('file', importFile);
- formData.append('format', 'opml');
-
- apiFetch('/api/import', {
- method: 'POST',
- body: formData,
- })
- .then((res) => {
- if (!res.ok) throw new Error('Failed to import feeds');
- return res.json();
- })
- .then(() => {
- setImportFile(null);
- fetchFeeds();
- alert('Import successful!');
- })
- .catch((err) => {
- setError(err.message);
- setLoading(false);
- });
- };
-
- const handleCrawl = () => {
- setLoading(true);
- apiFetch('/api/crawl', {
- method: 'POST',
- })
- .then((res) => {
- if (!res.ok) throw new Error('Failed to start crawl');
- return res.json();
- })
- .then(() => {
- setLoading(false);
- alert('Crawl started!');
- })
- .catch((err) => {
- setError(err.message);
- setLoading(false);
- });
- };
-
- return (
- <div className="settings-page variant-glass">
- <h2>Settings</h2>
-
- {setFontTheme && (
- <div className="appearance-section">
- <h3>Appearance</h3>
- <div className="font-selector">
- <label htmlFor="font-theme-select">Font Theme:</label>
- <select
- id="font-theme-select"
- value={fontTheme || 'default'}
- onChange={(e) => setFontTheme(e.target.value)}
- className="font-select"
- >
- <option value="default">Default</option>
- <option value="serif">Serif</option>
- <option value="sans">Sans-Serif</option>
- <option value="mono">Monospace</option>
- </select>
- </div>
- </div>
- )}
-
- <div className="add-feed-section">
- <h3>Add New Feed</h3>
- <form onSubmit={handleAddFeed} className="add-feed-form">
- <input
- type="url"
- value={newFeedUrl}
- onChange={(e) => setNewFeedUrl(e.target.value)}
- placeholder="https://example.com/feed.xml"
- required
- className="feed-input"
- disabled={loading}
- />
- <button type="submit" disabled={loading}>
- Add Feed
- </button>
- </form>
- </div>
-
- <div className="import-export-section">
- <div className="import-section">
- <h3>Import Feeds (OPML)</h3>
- <form onSubmit={handleImport} className="import-form">
- <input
- type="file"
- accept=".opml,.xml,.txt"
- aria-label="Import Feeds"
- onChange={(e) => setImportFile(e.target.files?.[0] || null)}
- className="file-input"
- disabled={loading}
- />
- <button type="submit" disabled={!importFile || loading}>
- Import
- </button>
- </form>
- </div>
-
- <div className="export-section">
- <h3>Export Feeds</h3>
- <div className="export-buttons">
- <a href="/api/export/opml" className="export-btn">OPML</a>
- <a href="/api/export/text" className="export-btn">Text</a>
- <a href="/api/export/json" className="export-btn">JSON</a>
- </div>
- </div>
-
- <div className="crawl-section">
- <h3>Actions</h3>
- <button onClick={handleCrawl} disabled={loading} className="crawl-btn">
- Crawl All Feeds Now
- </button>
- </div>
- </div>
-
- {error && <p className="error-message">{error}</p>}
-
- <div className="feed-list-section">
- <h3>Manage Feeds</h3>
- {loading && <p>Loading...</p>}
- <ul className="settings-feed-list">
- {feeds.map((feed) => (
- <li key={feed._id} className="settings-feed-item">
- <div className="feed-info">
- <span className="feed-title">{feed.title || '(No Title)'}</span>
- <span className="feed-url">{feed.url}</span>
- </div>
- <button
- onClick={() => handleDeleteFeed(feed._id)}
- className="delete-btn"
- disabled={loading}
- title="Delete Feed"
- >
- Delete
- </button>
- </li>
- ))}
- </ul>
- </div>
- </div>
- );
-}
diff --git a/frontend/src/components/TagView.test.tsx b/frontend/src/components/TagView.test.tsx
deleted file mode 100644
index de0a318..0000000
--- a/frontend/src/components/TagView.test.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import React from 'react';
-import { render, screen, waitFor } from '@testing-library/react';
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import { MemoryRouter, Route, Routes } from 'react-router-dom';
-import FeedList from './FeedList';
-import FeedItems from './FeedItems';
-
-describe('Tag View Integration', () => {
- beforeEach(() => {
- vi.resetAllMocks();
- global.fetch = vi.fn();
- });
-
- it('renders tags in FeedList and navigates to tag view', async () => {
- const mockFeeds = [
- { _id: 1, title: 'Feed 1', url: 'http://example.com/rss', category: 'Tech' },
- ];
- const mockTags = [{ title: 'Tech' }, { title: 'News' }];
-
- vi.mocked(global.fetch).mockImplementation((url) => {
- const urlStr = url.toString();
- if (urlStr.includes('/api/feed/')) {
- return Promise.resolve({
- ok: true,
- json: async () => mockFeeds,
- } as Response);
- }
- if (urlStr.includes('/api/tag')) {
- return Promise.resolve({
- ok: true,
- json: async () => mockTags,
- } as Response);
- }
- return Promise.reject(new Error(`Unknown URL: ${url}`));
- });
-
- render(
- <MemoryRouter>
- <FeedList
- theme="light"
- setTheme={() => { }}
- setSidebarVisible={() => { }}
- isMobile={false}
- />
- </MemoryRouter>
- );
-
- await waitFor(() => {
- const techTags = screen.getAllByText('Tech');
- expect(techTags.length).toBeGreaterThan(0);
- expect(screen.getByText('News')).toBeInTheDocument();
- });
-
- // Verify structure
- const techTag = screen.getByText('News').closest('a');
- expect(techTag).toHaveAttribute('href', '/tag/News?filter=unread');
- });
-
- it('fetches items by tag in FeedItems', async () => {
- const mockItems = [
- { _id: 101, title: 'Tag Item 1', url: 'http://example.com/1', feed_title: 'Feed 1' },
- ];
-
- vi.mocked(global.fetch).mockImplementation((url) => {
- const urlStr = url.toString();
- if (urlStr.includes('/api/stream')) {
- return Promise.resolve({
- ok: true,
- json: async () => mockItems,
- } as Response);
- }
- return Promise.reject(new Error(`Unknown URL: ${url}`));
- });
-
- render(
- <MemoryRouter initialEntries={['/tag/Tech']}>
- <Routes>
- <Route path="/tag/:tagName" element={<FeedItems />} />
- </Routes>
- </MemoryRouter>
- );
-
- await waitFor(() => {
- // expect(screen.getByText('Tag: Tech')).toBeInTheDocument();
- expect(screen.getByText('Tag Item 1')).toBeInTheDocument();
- });
-
- const params = new URLSearchParams();
- params.append('tag', 'Tech');
- params.append('read_filter', 'unread');
- expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`, expect.anything());
- });
-});
diff --git a/frontend/src/index.css b/frontend/src/index.css
deleted file mode 100644
index 22fc7d0..0000000
--- a/frontend/src/index.css
+++ /dev/null
@@ -1,158 +0,0 @@
-:root {
- /* Font Variables */
- --font-body: Palatino, 'Palatino Linotype', 'Palatino LT STD', 'Book Antiqua', Georgia, serif;
- --font-heading: 'Helvetica Neue', Helvetica, Arial, sans-serif;
-
- line-height: 1.5;
- font-size: 18px;
-
- /* Light Mode Defaults */
- --bg-color: #ffffff;
- --text-color: rgba(0, 0, 0, 0.87);
- --sidebar-bg: #ccc;
- --link-color: #0000ee;
- /* Standard blue link */
-
- color-scheme: light dark;
- color: var(--text-color);
- background-color: var(--bg-color);
-}
-
-html,
-body,
-#root {
- width: 100%;
- height: 100%;
- margin: 0;
- padding: 0;
- overflow: hidden;
-}
-
-body {
- font-family: var(--font-body);
-}
-
-h1,
-h2,
-h3,
-h4,
-h5,
-.logo,
-.nav-link,
-.logout-btn {
- font-family: var(--font-heading);
- font-weight: bold;
-}
-
-/* Font Themes */
-.font-default {
- /* Uses :root defaults */
- font-family: var(--font-body);
-}
-
-.font-serif {
- --font-body: Georgia, 'Times New Roman', Times, serif;
- --font-heading: Georgia, 'Times New Roman', Times, serif;
- font-family: var(--font-body);
-}
-
-.font-sans {
- --font-body: Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
- --font-heading: Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
- font-family: var(--font-body);
-}
-
-.font-mono {
- --font-body: Menlo, Monaco, Consolas, 'Courier New', monospace;
- --font-heading: Menlo, Monaco, Consolas, 'Courier New', monospace;
- font-family: var(--font-body);
-}
-
-
-.theme-light {
- --bg-color: #ffffff;
- --text-color: rgba(0, 0, 0, 0.87);
- --sidebar-bg: #ccc;
- --link-color: #0000ee;
- --border-color: #999;
- background-color: var(--bg-color);
- color: var(--text-color);
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --bg-color: #24292e;
- /* Legacy Dark */
- --text-color: #ffffff;
- --sidebar-bg: #1b1f23;
- /* Darker sidebar */
- --link-color: rgb(90, 200, 250);
- /* Legacy dark link */
- }
-}
-
-.theme-dark {
- --bg-color: #000000;
- --text-color: #ffffff;
- --sidebar-bg: #111111;
- --link-color: rgb(90, 200, 250);
- --border-color: #333;
- background-color: var(--bg-color);
- color: var(--text-color);
-}
-
-.theme-dark button {
- background-color: #333;
- color: #fff;
-}
-
-body {
- min-width: 320px;
-}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: bold;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-
-button:hover {
- border-color: #646cff;
-}
-
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-a {
- color: var(--link-color);
- text-decoration: none;
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
-
- a:hover {
- color: blue;
- text-decoration: underline;
- }
-
- button {
- background-color: #f9f9f9;
- }
-} \ No newline at end of file
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
deleted file mode 100644
index df655ea..0000000
--- a/frontend/src/main.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
-import './index.css';
-import App from './App.tsx';
-
-createRoot(document.getElementById('root')!).render(
- <StrictMode>
- <App />
- </StrictMode>
-);
diff --git a/frontend/src/setupTests.ts b/frontend/src/setupTests.ts
deleted file mode 100644
index 5781184..0000000
--- a/frontend/src/setupTests.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import '@testing-library/jest-dom';
-
-// Mock IntersectionObserver
-class IntersectionObserver {
- readonly root: Element | null = null;
- readonly rootMargin: string = '';
- readonly thresholds: ReadonlyArray<number> = [];
-
- constructor(_callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) {
- // nothing
- }
-
- observe(_target: Element): void {
- // nothing
- }
-
- unobserve(_target: Element): void {
- // nothing
- }
-
- disconnect(): void {
- // nothing
- }
-
- takeRecords(): IntersectionObserverEntry[] {
- return [];
- }
-}
-
-Object.defineProperty(window, 'IntersectionObserver', {
- writable: true,
- configurable: true,
- value: IntersectionObserver,
-});
-
-Object.defineProperty(globalThis, 'IntersectionObserver', {
- writable: true,
- configurable: true,
- value: IntersectionObserver,
-});
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
deleted file mode 100644
index 1feea1f..0000000
--- a/frontend/src/types.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-export interface Feed {
- _id: number;
- url: string;
- web_url: string;
- title: string;
- category: string;
-}
-
-export interface Item {
- _id: number;
- feed_id: number;
- title: string;
- url: string;
- description: string;
- publish_date: string;
- read: boolean;
- starred: boolean;
- full_content?: string;
- header_image?: string;
- feed_title?: string;
-}
-export interface Category {
- title: string;
-}
diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts
deleted file mode 100644
index ebfb692..0000000
--- a/frontend/src/utils.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-export function getCookie(name: string): string | undefined {
- const value = `; ${document.cookie}`;
- const parts = value.split(`; ${name}=`);
- if (parts.length === 2) return parts.pop()?.split(';').shift();
-}
-
-/**
- * A wrapper around fetch that automatically includes the CSRF token
- * for state-changing requests (POST, PUT, DELETE).
- */
-export async function apiFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
- const method = init?.method?.toUpperCase() || 'GET';
- const isStateChanging = ['POST', 'PUT', 'DELETE'].includes(method);
-
- const headers = new Headers(init?.headers || {});
-
- if (isStateChanging) {
- const token = getCookie('csrf_token');
- if (token) {
- headers.set('X-CSRF-Token', token);
- }
- }
-
- // Ensure requests are treated as coming from our own origin if needed,
- // but for a same-origin API, standard fetch defaults are usually fine.
-
- return fetch(input, {
- ...init,
- headers,
- credentials: 'include', // Ensure cookies are sent
- });
-}
diff --git a/frontend/tests/auth.spec.ts b/frontend/tests/auth.spec.ts
deleted file mode 100644
index 33ada85..0000000
--- a/frontend/tests/auth.spec.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-import { test, expect } from '@playwright/test';
-
-/**
- * E2E tests for authentication flows.
- *
- * These tests verify login behavior both with and without a password configured.
- * The current setup assumes no password (default for dev), so the password-required
- * tests are marked as skip. To run those, start the backend with --password=testpass.
- */
-
-test.describe('Authentication - No Password Required', () => {
- test('should allow direct access to dashboard without login', async ({ page }) => {
- // When no password is configured, users should be able to access
- // the dashboard directly without seeing the login page
- await page.goto('/v2/');
-
- // Should not redirect to login
- await expect(page).toHaveURL(/.*\/v2\/?$/);
-
- // Should see the dashboard elements
- await expect(page.locator('h1.logo')).toContainText('🐱');
- await expect(page.getByText('Logout')).toBeVisible();
- });
-
- test('should allow login with empty password', async ({ page }) => {
- // Visit login page
- await page.goto('/v2/login');
-
- // Fill username and submit with empty password
- await page.fill('input[id="username"]', 'neko');
- await page.click('button[type="submit"]');
-
- // Should redirect to dashboard
- await expect(page).toHaveURL(/.*\/v2\/?$/, { timeout: 5000 });
- await expect(page.locator('h1.logo')).toContainText('🐱');
- });
-
- test('should report authenticated status via API when no password', async ({ request }) => {
- // Check auth status
- const response = await request.get('/api/auth');
- expect(response.ok()).toBeTruthy();
-
- const data = await response.json();
- expect(data.authenticated).toBe(true);
- });
-});
-
-test.describe('Authentication - Password Required', () => {
- // These tests require the backend to be started with a password
- // Example: neko --password=testpass
- // Skip by default since dev environment has no password
-
- test.skip('should redirect to login when accessing protected routes', async ({ page, context }) => {
- // Clear any existing cookies
- await context.clearCookies();
-
- // Try to access dashboard
- await page.goto('/v2/');
-
- // Should redirect to login
- await expect(page).toHaveURL(/.*\/login/, { timeout: 5000 });
- });
-
- test.skip('should reject incorrect password', async ({ page }) => {
- await page.goto('/v2/login');
-
- // Enter wrong password
- await page.fill('input[id="username"]', 'neko');
- await page.fill('input[type="password"]', 'wrongpassword');
- await page.click('button[type="submit"]');
-
- // Should show error message
- await expect(page.getByText(/bad credentials|login failed/i)).toBeVisible({ timeout: 3000 });
-
- // Should still be on login page
- await expect(page).toHaveURL(/.*\/login/);
- });
-
- test.skip('should accept correct password and redirect to dashboard', async ({ page }) => {
- await page.goto('/v2/login');
-
- // Enter correct password (must match what the server was started with)
- await page.fill('input[id="username"]', 'neko');
- await page.fill('input[type="password"]', 'testpass');
- await page.click('button[type="submit"]');
-
- // Should redirect to dashboard
- await expect(page).toHaveURL(/.*\/v2\/?$/, { timeout: 5000 });
- await expect(page.locator('h1.logo')).toContainText('🐱');
- await expect(page.getByText('Logout')).toBeVisible();
- });
-
- test.skip('should persist authentication across page reloads', async ({ page }) => {
- // Login first
- await page.goto('/v2/login');
- await page.fill('input[id="username"]', 'neko');
- await page.fill('input[type="password"]', 'testpass');
- await page.click('button[type="submit"]');
- await expect(page).toHaveURL(/.*\/v2\/?$/);
-
- // Reload the page
- await page.reload();
-
- // Should still be authenticated (not redirected to login)
- await expect(page).toHaveURL(/.*\/v2\/?$/);
- await expect(page.locator('h1.logo')).toContainText('🐱');
- });
-
- test.skip('should logout and redirect to login page', async ({ page }) => {
- // Login first
- await page.goto('/v2/login');
- await page.fill('input[id="username"]', 'neko');
- await page.fill('input[type="password"]', 'testpass');
- await page.click('button[type="submit"]');
- await expect(page).toHaveURL(/.*\/v2\/?$/);
-
- // Click logout
- await page.click('text=Logout');
-
- // Should redirect to login
- await expect(page).toHaveURL(/.*\/login/);
-
- // Try to access dashboard again - should redirect to login
- await page.goto('/v2/');
- await expect(page).toHaveURL(/.*\/login/);
- });
-
- test.skip('should report unauthenticated status via API', async ({ request, context }) => {
- // Clear cookies
- await context.clearCookies();
-
- // Check auth status
- const response = await request.get('/api/auth');
- expect(response.ok()).toBeTruthy();
-
- const data = await response.json();
- expect(data.authenticated).toBe(false);
- });
-});
-
-test.describe('Authentication - Complete Flow', () => {
- test('should handle complete user flow without password', async ({ page }) => {
- // 1. Access dashboard directly
- await page.goto('/v2/');
- await expect(page.locator('h1.logo')).toContainText('🐱');
-
- // 2. Navigate to settings
- await page.click('text=Settings');
- await expect(page).toHaveURL(/.*\/settings/);
-
- // 3. Add a feed (this tests that API calls work when no password)
- const feedUrl = 'http://example.com/rss.xml';
- await page.fill('input[type="url"]', feedUrl);
- await page.click('text=Add Feed');
-
- // Wait for success (feed should appear)
- await expect(page.getByText(feedUrl)).toBeVisible({ timeout: 3000 });
-
- // 4. Navigate back to main view
- await page.goto('/v2/');
- await expect(page.locator('h1.logo')).toContainText('🐱');
-
- // 5. Logout (should work even with no password)
- await page.click('text=Logout');
- await expect(page).toHaveURL(/.*\/login/);
- });
-});
diff --git a/frontend/tests/crawl.spec.ts b/frontend/tests/crawl.spec.ts
deleted file mode 100644
index 175a764..0000000
--- a/frontend/tests/crawl.spec.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { test, expect } from '@playwright/test';
-
-test.describe('Crawl Integration', () => {
- test('should add a feed and see items after crawl', async ({ page }) => {
- const mockFeedUrl = 'http://localhost:9090/mock_feed.xml';
-
- // 1. Login and go to Settings
- await page.goto('/v2/settings');
-
- // 2. Add the mock feed
- await page.fill('input[type="url"]', mockFeedUrl);
- await page.click('text=Add Feed');
-
- // Wait for feed to be added
- await expect(page.getByText(mockFeedUrl)).toBeVisible({ timeout: 5000 });
-
- // 3. Trigger Crawl
- const crawlButton = page.getByRole('button', { name: /crawl/i });
- await expect(crawlButton).toBeVisible();
-
- // Handle the alert
- page.on('dialog', dialog => dialog.accept());
- await crawlButton.click();
-
- // 4. Go to Home and check for items
- await page.goto('/v2/');
-
- // The mock feed has "Mock Item 1" and "Mock Item 2"
- await expect(page.getByText('Mock Item 1')).toBeVisible({ timeout: 10000 });
- await expect(page.getByText('Mock Item 2')).toBeVisible({ timeout: 10000 });
- });
-});
diff --git a/frontend/tests/e2e.spec.ts b/frontend/tests/e2e.spec.ts
deleted file mode 100644
index 2bdf478..0000000
--- a/frontend/tests/e2e.spec.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { test, expect } from '@playwright/test';
-
-test.describe('Neko Reader E2E', () => {
- test('should allow login, viewing feeds, and logout', async ({ page }) => {
- // 1. Go to Login
- await page.goto('/v2/login');
- await expect(page).toHaveTitle(/Neko/);
-
- // 2. Login
- // 2. Login
- // Password is empty by default in test env
- await page.click('button[type="submit"]');
-
- // Check for error message if login failed (optional, for debugging)
- // await expect(page.locator('.error-message')).toBeVisible({ timeout: 2000 }).catch(() => {});
-
- // 3. Verify Dashboard
- // Keep checking for /v2/ or /v2
- await expect(page).toHaveURL(/.*\/v2\/?$/);
- await expect(page.locator('h1.logo')).toContainText('🐱');
- await expect(page.getByText('Logout')).toBeVisible();
-
- // 4. Verify Feed List
- await page.click('text=Settings');
- await expect(page).toHaveURL(/.*\/v2\/settings/);
-
- // Add a feed
- const feedUrl = 'http://localhost:9090/mock_feed.xml';
- await page.fill('input[type="url"]', feedUrl);
- await page.click('text=Add Feed');
-
- // Wait for it to appear
- await expect(page.getByText(feedUrl)).toBeVisible();
-
- const waitForLoader = async () => {
- await page.waitForFunction(() => {
- const loading = document.querySelector('.feed-items-loading') ||
- document.body.innerText.includes('Loading...');
- return !loading;
- }, { timeout: 15000 });
- };
-
- // 5. Navigate to Feed
- console.log('Step 5: Navigate to Home');
- await page.goto('/v2/');
- await expect(page).toHaveURL(/.*\/v2\/?$/);
- // Default view is now the stream.
- // It should NOT show "Select a feed" anymore.
- // Wait for items or "No items found" or loading state
- await waitForLoader();
- await expect(
- page
- .locator('.feed-items')
- .or(page.getByText('No items found'))
- .or(page.locator('.feed-items-error'))
- ).toBeVisible({ timeout: 10000 });
-
- // 6. Verify Tag View
- // 6. Logout
- console.log('Step 6: Logout');
- await page.click('text=Logout');
- await expect(page).toHaveURL(/.*\/v2\/login/);
- });
-});
diff --git a/frontend/tests/font.spec.ts b/frontend/tests/font.spec.ts
deleted file mode 100644
index 0723f38..0000000
--- a/frontend/tests/font.spec.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { test, expect } from '@playwright/test';
-
-test.describe('Font Theme Settings', () => {
- test('should change font family when theme starts', async ({ page }) => {
- // 1. Login
- await page.goto('/v2/login');
- await page.click('button[type="submit"]');
- await expect(page).toHaveURL(/.*\/v2\/?$/);
-
- // 2. Go to Settings
- await page.click('text=Settings');
- await expect(page).toHaveURL(/.*\/v2\/settings/);
-
- // 3. Verify Default Font (Palatino)
- // We check the computed style of the dashboard container or a body element
- const dashboard = page.locator('.dashboard');
- await expect(dashboard).toHaveCSS('font-family', /Palatino/);
-
- // 4. Change to Sans-Serif
- await page.selectOption('select.font-select', 'sans');
-
- // 5. Verify Sans Font (Inter)
- await expect(dashboard).toHaveCSS('font-family', /Inter/);
-
- // 6. Change to Monospace
- await page.selectOption('select.font-select', 'mono');
-
- // 7. Verify Mono Font (Menlo or Monaco or Courier)
- await expect(dashboard).toHaveCSS('font-family', /Menlo|Monaco|Courier/);
- });
-});
diff --git a/frontend/tests/mocked-api.spec.ts b/frontend/tests/mocked-api.spec.ts
deleted file mode 100644
index 06f6c17..0000000
--- a/frontend/tests/mocked-api.spec.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-import { test, expect } from '@playwright/test';
-
-test.describe('Mocked API UI Tests', () => {
- test.beforeEach(async ({ page }) => {
- // Log browser console for debugging
- page.on('console', msg => {
- if (msg.type() === 'error') console.log(`BROWSER ERROR: ${msg.text()} `);
- });
-
- // 1. Mock Auth - simulate logged in session
- await page.route('**/api/auth', async (route) => {
- await route.fulfill({
- status: 200,
- contentType: 'application/json',
- body: JSON.stringify({ status: 'ok', authenticated: true }),
- });
- });
-
- // 2. Mock Feeds
- await page.route('**/api/feed/', async (route) => {
- await route.fulfill({
- status: 200,
- contentType: 'application/json',
- body: JSON.stringify([
- { _id: 1, title: 'Mock Feed 1', url: 'http://mock1.com', category: 'News' },
- { _id: 2, title: 'Mock Feed 2', url: 'http://mock2.com', category: 'Tech' },
- ]),
- });
- });
-
- // 3. Mock Tags
- await page.route('**/api/tag', async (route) => {
- await route.fulfill({
- status: 200,
- contentType: 'application/json',
- body: JSON.stringify([
- { title: 'News' },
- { title: 'Tech' },
- ]),
- });
- });
-
- // 4. Mock Stream/Items
- await page.route('**/api/stream*', async (route) => {
- const url = new URL(route.request().url());
- const maxId = url.searchParams.get('max_id');
-
- if (maxId) {
- await route.fulfill({
- status: 200,
- contentType: 'application/json',
- body: JSON.stringify([]),
- });
- return;
- }
-
- await route.fulfill({
- status: 200,
- contentType: 'application/json',
- body: JSON.stringify([
- {
- _id: 101,
- feed_id: 1,
- title: 'Mock Item Unread',
- url: 'http://mock1.com/1',
- description: 'This is an unread item',
- publish_date: new Date().toISOString(),
- read: false,
- starred: false,
- feed_title: 'Mock Feed 1'
- },
- {
- _id: 102,
- feed_id: 2,
- title: 'Mock Item Starred',
- url: 'http://mock2.com/1',
- description: 'This is a starred and read item',
- publish_date: new Date().toISOString(),
- read: true,
- starred: true,
- feed_title: 'Mock Feed 2'
- }
- ]),
- });
- });
-
- // 5. Mock Item Update (for marking read/starred)
- await page.route('**/api/item/**', async (route) => {
- if (route.request().method() === 'PUT') {
- await route.fulfill({
- status: 200,
- contentType: 'application/json',
- body: JSON.stringify({ status: 'ok' }),
- });
- } else {
- await route.continue();
- }
- });
-
- // 6. Mock Logout
- await page.route('**/api/logout', async (route) => {
- await route.fulfill({
- status: 200,
- contentType: 'application/json',
- body: JSON.stringify({ status: 'ok' }),
- });
- });
- });
-
- test('should load dashboard with mocked feeds and items', async ({ page }) => {
- await page.goto('/v2/');
-
- // Wait for the logo to appear (means we are on the dashboard)
- await expect(page.locator('h1.logo')).toBeVisible({ timeout: 15000 });
-
- // Verify items in main view (ensure they load first)
- await expect(page.getByText('Mock Item Unread')).toBeVisible({ timeout: 10000 });
- await expect(page.getByText('Mock Item Starred')).toBeVisible();
-
- // Verify feeds in sidebar
- // Click on the Feeds header to expand
- await page.getByText(/Feeds/i).click();
-
- // Wait for mocked feeds to appear in sidebar
- // We use a more specific selector to avoid matching the feed_title in the item list
- await expect(page.locator('.feed-list-items').getByText('Mock Feed 1')).toBeVisible({ timeout: 10000 });
- await expect(page.locator('.feed-list-items').getByText('Mock Feed 2')).toBeVisible();
-
- // Verify "unread" filter is active by default
- const unreadFilterLink = page.locator('.unread_filter a');
- await expect(unreadFilterLink).toHaveClass(/active/);
- });
-
- test('should filter by mocked tag', async ({ page }) => {
- await page.goto('/v2/');
-
- // Click on Tech tag
- await page.getByText('Tech', { exact: true }).click();
-
- // URL should update
- await expect(page).toHaveURL(/.*\/tag\/Tech/);
-
- // Verify feed items are still visible (mock stream returns both regardless of tag in this simple mock)
- await expect(page.getByText('Mock Item Unread')).toBeVisible();
- });
-
- test('should toggle item star status', async ({ page }) => {
- await page.goto('/v2/');
-
- const unreadItem = page.locator('.feed-item.unread').first();
- const starButton = unreadItem.getByTitle('Star');
-
- await starButton.click();
-
- // Expect star to change to "Unstar" (UI optimistic update)
- await expect(starButton).toHaveAttribute('title', 'Unstar');
- });
-
- test('should logout using mocked API', async ({ page }) => {
- await page.goto('/v2/');
-
- await page.getByText('logout').click();
-
- // Should redirect to login
- await expect(page).toHaveURL(/.*\/login/);
- });
-});
diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json
deleted file mode 100644
index 3fcc60b..0000000
--- a/frontend/tsconfig.app.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
- "target": "ES2022",
- "useDefineForClassFields": true,
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
- "module": "ESNext",
- "types": ["vite/client"],
- "skipLibCheck": true,
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "verbatimModuleSyntax": true,
- "moduleDetection": "force",
- "noEmit": true,
- "jsx": "react-jsx",
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "erasableSyntaxOnly": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["src"],
- "exclude": ["**/*.test.tsx", "**/*.test.ts"]
-}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
deleted file mode 100644
index d32ff68..0000000
--- a/frontend/tsconfig.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "files": [],
- "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
-}
diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json
deleted file mode 100644
index 50145d1..0000000
--- a/frontend/tsconfig.node.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
- "target": "ES2023",
- "lib": ["ES2023"],
- "module": "ESNext",
- "types": ["node", "vitest"],
- "skipLibCheck": true,
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "verbatimModuleSyntax": true,
- "moduleDetection": "force",
- "noEmit": true,
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "erasableSyntaxOnly": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["vite.config.ts"]
-}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
deleted file mode 100644
index 025cbb3..0000000
--- a/frontend/vite.config.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/// <reference types="vitest" />
-import { defineConfig } from 'vite';
-import react from '@vitejs/plugin-react';
-
-// https://vite.dev/config/
-export default defineConfig({
- plugins: [react()],
- base: '/v2/',
- server: {
- proxy: {
- '/api': 'http://127.0.0.1:4994',
- '/image': 'http://127.0.0.1:4994',
- },
- },
-});
diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts
deleted file mode 100644
index 9cb79ae..0000000
--- a/frontend/vitest.config.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/// <reference types="vitest" />
-import { defineConfig } from 'vite';
-
-export default defineConfig({
- test: {
- globals: true,
- environment: 'jsdom',
- setupFiles: './src/setupTests.ts',
- exclude: ['**/node_modules/**', '**/dist/**', '**/tests/**'],
- },
-});