taskchef 8.6.2 → 8.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +8 -4
- package/assets/README.md +19 -0
- package/assets/taskchef-app-rounded.png +0 -0
- package/assets/taskchef-avatar-square.png +0 -0
- package/assets/taskchef-avatar-square.svg +8 -0
- package/assets/taskchef-sidebar-dark.svg +6 -0
- package/assets/taskchef-sidebar-light.svg +6 -0
- package/assets/taskchef-sidebar.svg +6 -0
- package/docs/advanced-guide.md +20 -29
- package/docs/dashboard-ui.md +15 -0
- package/docs/diagrams/taskchef-next-reply-lookup.drawio +38 -0
- package/docs/diagrams/taskchef-next-reply-lookup.drawio.png +0 -0
- package/docs/diagrams/taskchef-next-storage.drawio +41 -0
- package/docs/diagrams/taskchef-next-storage.drawio.png +0 -0
- package/docs/images/taskchef-pr-icon-comparison.jpg +0 -0
- package/docs/images/taskchef-unified-status-popup.jpg +0 -0
- package/docs/mcp-app.md +278 -6
- package/docs/spec.md +24 -69
- package/docs/taskchef-next-github.md +206 -0
- package/docs/taskchef-next-status-research.md +102 -0
- package/docs/taskchef-next-storage.md +167 -0
- package/docs/workflows.md +4 -2
- package/node_modules/@napi-rs/keyring/LICENSE +21 -0
- package/node_modules/@napi-rs/keyring/README.md +45 -0
- package/node_modules/@napi-rs/keyring/index.d.ts +232 -0
- package/node_modules/@napi-rs/keyring/index.js +706 -0
- package/node_modules/@napi-rs/keyring/keytar.d.ts +53 -0
- package/node_modules/@napi-rs/keyring/keytar.js +26 -0
- package/node_modules/@napi-rs/keyring/package.json +126 -0
- package/node_modules/@napi-rs/keyring-darwin-arm64/README.md +3 -0
- package/node_modules/@napi-rs/keyring-darwin-arm64/keyring.darwin-arm64.node +0 -0
- package/node_modules/@napi-rs/keyring-darwin-arm64/package.json +32 -0
- package/node_modules/@napi-rs/keyring-darwin-x64/README.md +3 -0
- package/node_modules/@napi-rs/keyring-darwin-x64/keyring.darwin-x64.node +0 -0
- package/node_modules/@napi-rs/keyring-darwin-x64/package.json +32 -0
- package/node_modules/@napi-rs/keyring-linux-arm64-gnu/README.md +3 -0
- package/node_modules/@napi-rs/keyring-linux-arm64-gnu/keyring.linux-arm64-gnu.node +0 -0
- package/node_modules/@napi-rs/keyring-linux-arm64-gnu/package.json +35 -0
- package/node_modules/@napi-rs/keyring-linux-arm64-musl/README.md +3 -0
- package/node_modules/@napi-rs/keyring-linux-arm64-musl/keyring.linux-arm64-musl.node +0 -0
- package/node_modules/@napi-rs/keyring-linux-arm64-musl/package.json +35 -0
- package/node_modules/@napi-rs/keyring-linux-x64-gnu/README.md +3 -0
- package/node_modules/@napi-rs/keyring-linux-x64-gnu/keyring.linux-x64-gnu.node +0 -0
- package/node_modules/@napi-rs/keyring-linux-x64-gnu/package.json +35 -0
- package/node_modules/@napi-rs/keyring-linux-x64-musl/README.md +3 -0
- package/node_modules/@napi-rs/keyring-linux-x64-musl/keyring.linux-x64-musl.node +0 -0
- package/node_modules/@napi-rs/keyring-linux-x64-musl/package.json +35 -0
- package/node_modules/@napi-rs/keyring-win32-arm64-msvc/README.md +3 -0
- package/node_modules/@napi-rs/keyring-win32-arm64-msvc/keyring.win32-arm64-msvc.node +0 -0
- package/node_modules/@napi-rs/keyring-win32-arm64-msvc/package.json +32 -0
- package/node_modules/@napi-rs/keyring-win32-x64-msvc/README.md +3 -0
- package/node_modules/@napi-rs/keyring-win32-x64-msvc/keyring.win32-x64-msvc.node +0 -0
- package/node_modules/@napi-rs/keyring-win32-x64-msvc/package.json +32 -0
- package/node_modules/@types/debug/LICENSE +21 -0
- package/node_modules/@types/debug/README.md +69 -0
- package/node_modules/@types/debug/index.d.ts +50 -0
- package/node_modules/@types/debug/package.json +58 -0
- package/node_modules/@types/mdast/LICENSE +21 -0
- package/node_modules/@types/mdast/README.md +15 -0
- package/node_modules/@types/mdast/index.d.ts +1123 -0
- package/node_modules/@types/mdast/package.json +47 -0
- package/node_modules/@types/ms/LICENSE +21 -0
- package/node_modules/@types/ms/README.md +82 -0
- package/node_modules/@types/ms/index.d.ts +63 -0
- package/node_modules/@types/ms/package.json +26 -0
- package/node_modules/@types/unist/LICENSE +21 -0
- package/node_modules/@types/unist/README.md +15 -0
- package/node_modules/@types/unist/index.d.ts +119 -0
- package/node_modules/@types/unist/package.json +60 -0
- package/node_modules/character-entities/index.d.ts +6 -0
- package/node_modules/character-entities/index.js +2132 -0
- package/node_modules/character-entities/license +22 -0
- package/node_modules/character-entities/package.json +78 -0
- package/node_modules/character-entities/readme.md +152 -0
- package/node_modules/decode-named-character-reference/index.d.ts +13 -0
- package/node_modules/decode-named-character-reference/index.d.ts.map +1 -0
- package/node_modules/decode-named-character-reference/index.dom.d.ts +6 -0
- package/node_modules/decode-named-character-reference/index.dom.d.ts.map +1 -0
- package/node_modules/decode-named-character-reference/index.dom.js +32 -0
- package/node_modules/decode-named-character-reference/index.js +19 -0
- package/node_modules/decode-named-character-reference/license +22 -0
- package/node_modules/decode-named-character-reference/package.json +90 -0
- package/node_modules/decode-named-character-reference/readme.md +136 -0
- package/node_modules/dequal/dist/index.js +86 -0
- package/node_modules/dequal/dist/index.min.js +1 -0
- package/node_modules/dequal/dist/index.mjs +84 -0
- package/node_modules/dequal/index.d.ts +1 -0
- package/node_modules/dequal/license +21 -0
- package/node_modules/dequal/lite/index.d.ts +1 -0
- package/node_modules/dequal/lite/index.js +31 -0
- package/node_modules/dequal/lite/index.min.js +1 -0
- package/node_modules/dequal/lite/index.mjs +29 -0
- package/node_modules/dequal/package.json +57 -0
- package/node_modules/dequal/readme.md +112 -0
- package/node_modules/devlop/lib/default.js +9 -0
- package/node_modules/devlop/lib/development.d.ts +84 -0
- package/node_modules/devlop/lib/development.js +238 -0
- package/node_modules/devlop/license +22 -0
- package/node_modules/devlop/package.json +80 -0
- package/node_modules/devlop/readme.md +360 -0
- package/node_modules/mdast-util-from-markdown/dev/index.d.ts +23 -0
- package/node_modules/mdast-util-from-markdown/dev/index.js +2 -0
- package/node_modules/mdast-util-from-markdown/dev/lib/index.d.ts +53 -0
- package/node_modules/mdast-util-from-markdown/dev/lib/index.d.ts.map +1 -0
- package/node_modules/mdast-util-from-markdown/dev/lib/index.js +1348 -0
- package/node_modules/mdast-util-from-markdown/dev/lib/types.d.ts +295 -0
- package/node_modules/mdast-util-from-markdown/dev/lib/types.js +2 -0
- package/node_modules/mdast-util-from-markdown/index.d.ts +23 -0
- package/node_modules/mdast-util-from-markdown/index.js +2 -0
- package/node_modules/mdast-util-from-markdown/lib/index.d.ts +53 -0
- package/node_modules/mdast-util-from-markdown/lib/index.js +1177 -0
- package/node_modules/mdast-util-from-markdown/lib/types.d.ts +295 -0
- package/node_modules/mdast-util-from-markdown/lib/types.js +2 -0
- package/node_modules/mdast-util-from-markdown/license +22 -0
- package/node_modules/mdast-util-from-markdown/package.json +139 -0
- package/node_modules/mdast-util-from-markdown/readme.md +537 -0
- package/node_modules/mdast-util-to-string/index.d.ts +2 -0
- package/node_modules/mdast-util-to-string/index.js +5 -0
- package/node_modules/mdast-util-to-string/lib/index.d.ts +31 -0
- package/node_modules/mdast-util-to-string/lib/index.js +108 -0
- package/node_modules/mdast-util-to-string/license +22 -0
- package/node_modules/mdast-util-to-string/package.json +78 -0
- package/node_modules/mdast-util-to-string/readme.md +218 -0
- package/node_modules/micromark/dev/index.d.ts +82 -0
- package/node_modules/micromark/dev/index.d.ts.map +1 -0
- package/node_modules/micromark/dev/index.js +68 -0
- package/node_modules/micromark/dev/lib/compile.d.ts +16 -0
- package/node_modules/micromark/dev/lib/compile.d.ts.map +1 -0
- package/node_modules/micromark/dev/lib/compile.js +1162 -0
- package/node_modules/micromark/dev/lib/constructs.d.ts +73 -0
- package/node_modules/micromark/dev/lib/constructs.d.ts.map +1 -0
- package/node_modules/micromark/dev/lib/constructs.js +101 -0
- package/node_modules/micromark/dev/lib/create-tokenizer.d.ts +46 -0
- package/node_modules/micromark/dev/lib/create-tokenizer.d.ts.map +1 -0
- package/node_modules/micromark/dev/lib/create-tokenizer.js +718 -0
- package/node_modules/micromark/dev/lib/initialize/content.d.ts +4 -0
- package/node_modules/micromark/dev/lib/initialize/content.d.ts.map +1 -0
- package/node_modules/micromark/dev/lib/initialize/content.js +99 -0
- package/node_modules/micromark/dev/lib/initialize/document.d.ts +10 -0
- package/node_modules/micromark/dev/lib/initialize/document.d.ts.map +1 -0
- package/node_modules/micromark/dev/lib/initialize/document.js +449 -0
- package/node_modules/micromark/dev/lib/initialize/flow.d.ts +4 -0
- package/node_modules/micromark/dev/lib/initialize/flow.d.ts.map +1 -0
- package/node_modules/micromark/dev/lib/initialize/flow.js +86 -0
- package/node_modules/micromark/dev/lib/initialize/text.d.ts +8 -0
- package/node_modules/micromark/dev/lib/initialize/text.d.ts.map +1 -0
- package/node_modules/micromark/dev/lib/initialize/text.js +248 -0
- package/node_modules/micromark/dev/lib/parse.d.ts +10 -0
- package/node_modules/micromark/dev/lib/parse.d.ts.map +1 -0
- package/node_modules/micromark/dev/lib/parse.js +58 -0
- package/node_modules/micromark/dev/lib/postprocess.d.ts +9 -0
- package/node_modules/micromark/dev/lib/postprocess.d.ts.map +1 -0
- package/node_modules/micromark/dev/lib/postprocess.js +19 -0
- package/node_modules/micromark/dev/lib/preprocess.d.ts +13 -0
- package/node_modules/micromark/dev/lib/preprocess.d.ts.map +1 -0
- package/node_modules/micromark/dev/lib/preprocess.js +139 -0
- package/node_modules/micromark/dev/stream.d.ts +35 -0
- package/node_modules/micromark/dev/stream.d.ts.map +1 -0
- package/node_modules/micromark/dev/stream.js +270 -0
- package/node_modules/micromark/index.d.ts +82 -0
- package/node_modules/micromark/index.d.ts.map +1 -0
- package/node_modules/micromark/index.js +60 -0
- package/node_modules/micromark/lib/compile.d.ts +16 -0
- package/node_modules/micromark/lib/compile.d.ts.map +1 -0
- package/node_modules/micromark/lib/compile.js +1068 -0
- package/node_modules/micromark/lib/constructs.d.ts +73 -0
- package/node_modules/micromark/lib/constructs.d.ts.map +1 -0
- package/node_modules/micromark/lib/constructs.js +85 -0
- package/node_modules/micromark/lib/create-tokenizer.d.ts +46 -0
- package/node_modules/micromark/lib/create-tokenizer.d.ts.map +1 -0
- package/node_modules/micromark/lib/create-tokenizer.js +613 -0
- package/node_modules/micromark/lib/initialize/content.d.ts +4 -0
- package/node_modules/micromark/lib/initialize/content.d.ts.map +1 -0
- package/node_modules/micromark/lib/initialize/content.js +79 -0
- package/node_modules/micromark/lib/initialize/document.d.ts +10 -0
- package/node_modules/micromark/lib/initialize/document.d.ts.map +1 -0
- package/node_modules/micromark/lib/initialize/document.js +370 -0
- package/node_modules/micromark/lib/initialize/flow.d.ts +4 -0
- package/node_modules/micromark/lib/initialize/flow.d.ts.map +1 -0
- package/node_modules/micromark/lib/initialize/flow.js +58 -0
- package/node_modules/micromark/lib/initialize/text.d.ts +8 -0
- package/node_modules/micromark/lib/initialize/text.d.ts.map +1 -0
- package/node_modules/micromark/lib/initialize/text.js +216 -0
- package/node_modules/micromark/lib/parse.d.ts +10 -0
- package/node_modules/micromark/lib/parse.d.ts.map +1 -0
- package/node_modules/micromark/lib/parse.js +56 -0
- package/node_modules/micromark/lib/postprocess.d.ts +9 -0
- package/node_modules/micromark/lib/postprocess.d.ts.map +1 -0
- package/node_modules/micromark/lib/postprocess.js +18 -0
- package/node_modules/micromark/lib/preprocess.d.ts +13 -0
- package/node_modules/micromark/lib/preprocess.d.ts.map +1 -0
- package/node_modules/micromark/lib/preprocess.js +112 -0
- package/node_modules/micromark/license +22 -0
- package/node_modules/micromark/package.json +87 -0
- package/node_modules/micromark/readme.md +488 -0
- package/node_modules/micromark/stream.d.ts +35 -0
- package/node_modules/micromark/stream.d.ts.map +1 -0
- package/node_modules/micromark/stream.js +256 -0
- package/node_modules/micromark-core-commonmark/dev/index.d.ts +23 -0
- package/node_modules/micromark-core-commonmark/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/index.js +22 -0
- package/node_modules/micromark-core-commonmark/dev/lib/attention.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/attention.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/attention.js +294 -0
- package/node_modules/micromark-core-commonmark/dev/lib/autolink.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/autolink.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/autolink.js +269 -0
- package/node_modules/micromark-core-commonmark/dev/lib/blank-line.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/blank-line.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/blank-line.js +62 -0
- package/node_modules/micromark-core-commonmark/dev/lib/block-quote.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/block-quote.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/block-quote.js +162 -0
- package/node_modules/micromark-core-commonmark/dev/lib/character-escape.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/character-escape.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/character-escape.js +69 -0
- package/node_modules/micromark-core-commonmark/dev/lib/character-reference.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/character-reference.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/character-reference.js +166 -0
- package/node_modules/micromark-core-commonmark/dev/lib/code-fenced.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/code-fenced.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/code-fenced.js +472 -0
- package/node_modules/micromark-core-commonmark/dev/lib/code-indented.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/code-indented.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/code-indented.js +178 -0
- package/node_modules/micromark-core-commonmark/dev/lib/code-text.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/code-text.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/code-text.js +257 -0
- package/node_modules/micromark-core-commonmark/dev/lib/content.d.ts +7 -0
- package/node_modules/micromark-core-commonmark/dev/lib/content.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/content.js +185 -0
- package/node_modules/micromark-core-commonmark/dev/lib/definition.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/definition.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/definition.js +295 -0
- package/node_modules/micromark-core-commonmark/dev/lib/hard-break-escape.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/hard-break-escape.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/hard-break-escape.js +65 -0
- package/node_modules/micromark-core-commonmark/dev/lib/heading-atx.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/heading-atx.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/heading-atx.js +229 -0
- package/node_modules/micromark-core-commonmark/dev/lib/html-flow.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/html-flow.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/html-flow.js +938 -0
- package/node_modules/micromark-core-commonmark/dev/lib/html-text.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/html-text.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/html-text.js +783 -0
- package/node_modules/micromark-core-commonmark/dev/lib/label-end.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/label-end.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/label-end.js +658 -0
- package/node_modules/micromark-core-commonmark/dev/lib/label-start-image.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/label-start-image.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/label-start-image.js +119 -0
- package/node_modules/micromark-core-commonmark/dev/lib/label-start-link.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/label-start-link.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/label-start-link.js +71 -0
- package/node_modules/micromark-core-commonmark/dev/lib/line-ending.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/line-ending.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/line-ending.js +34 -0
- package/node_modules/micromark-core-commonmark/dev/lib/list.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/list.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/list.js +306 -0
- package/node_modules/micromark-core-commonmark/dev/lib/partial-non-lazy-continuation.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/partial-non-lazy-continuation.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/partial-non-lazy-continuation.js +67 -0
- package/node_modules/micromark-core-commonmark/dev/lib/setext-underline.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/setext-underline.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/setext-underline.js +213 -0
- package/node_modules/micromark-core-commonmark/dev/lib/thematic-break.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/dev/lib/thematic-break.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/dev/lib/thematic-break.js +120 -0
- package/node_modules/micromark-core-commonmark/index.d.ts +23 -0
- package/node_modules/micromark-core-commonmark/index.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/index.js +22 -0
- package/node_modules/micromark-core-commonmark/lib/attention.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/attention.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/attention.js +231 -0
- package/node_modules/micromark-core-commonmark/lib/autolink.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/autolink.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/autolink.js +233 -0
- package/node_modules/micromark-core-commonmark/lib/blank-line.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/blank-line.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/blank-line.js +61 -0
- package/node_modules/micromark-core-commonmark/lib/block-quote.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/block-quote.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/block-quote.js +143 -0
- package/node_modules/micromark-core-commonmark/lib/character-escape.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/character-escape.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/character-escape.js +64 -0
- package/node_modules/micromark-core-commonmark/lib/character-reference.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/character-reference.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/character-reference.js +149 -0
- package/node_modules/micromark-core-commonmark/lib/code-fenced.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/code-fenced.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/code-fenced.js +422 -0
- package/node_modules/micromark-core-commonmark/lib/code-indented.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/code-indented.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/code-indented.js +159 -0
- package/node_modules/micromark-core-commonmark/lib/code-text.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/code-text.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/code-text.js +225 -0
- package/node_modules/micromark-core-commonmark/lib/content.d.ts +7 -0
- package/node_modules/micromark-core-commonmark/lib/content.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/content.js +163 -0
- package/node_modules/micromark-core-commonmark/lib/definition.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/definition.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/definition.js +254 -0
- package/node_modules/micromark-core-commonmark/lib/hard-break-escape.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/hard-break-escape.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/hard-break-escape.js +60 -0
- package/node_modules/micromark-core-commonmark/lib/heading-atx.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/heading-atx.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/heading-atx.js +191 -0
- package/node_modules/micromark-core-commonmark/lib/html-flow.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/html-flow.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/html-flow.js +827 -0
- package/node_modules/micromark-core-commonmark/lib/html-text.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/html-text.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/html-text.js +678 -0
- package/node_modules/micromark-core-commonmark/lib/label-end.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/label-end.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/label-end.js +561 -0
- package/node_modules/micromark-core-commonmark/lib/label-start-image.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/label-start-image.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/label-start-image.js +110 -0
- package/node_modules/micromark-core-commonmark/lib/label-start-link.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/label-start-link.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/label-start-link.js +63 -0
- package/node_modules/micromark-core-commonmark/lib/line-ending.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/line-ending.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/line-ending.js +33 -0
- package/node_modules/micromark-core-commonmark/lib/list.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/list.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/list.js +213 -0
- package/node_modules/micromark-core-commonmark/lib/partial-non-lazy-continuation.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/partial-non-lazy-continuation.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/partial-non-lazy-continuation.js +61 -0
- package/node_modules/micromark-core-commonmark/lib/setext-underline.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/setext-underline.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/setext-underline.js +187 -0
- package/node_modules/micromark-core-commonmark/lib/thematic-break.d.ts +4 -0
- package/node_modules/micromark-core-commonmark/lib/thematic-break.d.ts.map +1 -0
- package/node_modules/micromark-core-commonmark/lib/thematic-break.js +102 -0
- package/node_modules/micromark-core-commonmark/license +22 -0
- package/node_modules/micromark-core-commonmark/package.json +62 -0
- package/node_modules/micromark-core-commonmark/readme.md +171 -0
- package/node_modules/micromark-factory-destination/dev/index.d.ts +42 -0
- package/node_modules/micromark-factory-destination/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-factory-destination/dev/index.js +255 -0
- package/node_modules/micromark-factory-destination/index.d.ts +42 -0
- package/node_modules/micromark-factory-destination/index.d.ts.map +1 -0
- package/node_modules/micromark-factory-destination/index.js +206 -0
- package/node_modules/micromark-factory-destination/license +22 -0
- package/node_modules/micromark-factory-destination/package.json +57 -0
- package/node_modules/micromark-factory-destination/readme.md +234 -0
- package/node_modules/micromark-factory-label/dev/index.d.ts +37 -0
- package/node_modules/micromark-factory-label/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-factory-label/dev/index.js +172 -0
- package/node_modules/micromark-factory-label/index.d.ts +37 -0
- package/node_modules/micromark-factory-label/index.d.ts.map +1 -0
- package/node_modules/micromark-factory-label/index.js +148 -0
- package/node_modules/micromark-factory-label/license +22 -0
- package/node_modules/micromark-factory-label/package.json +60 -0
- package/node_modules/micromark-factory-label/readme.md +224 -0
- package/node_modules/micromark-factory-space/dev/index.d.ts +60 -0
- package/node_modules/micromark-factory-space/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-factory-space/dev/index.js +122 -0
- package/node_modules/micromark-factory-space/index.d.ts +60 -0
- package/node_modules/micromark-factory-space/index.d.ts.map +1 -0
- package/node_modules/micromark-factory-space/index.js +116 -0
- package/node_modules/micromark-factory-space/license +22 -0
- package/node_modules/micromark-factory-space/package.json +46 -0
- package/node_modules/micromark-factory-space/readme.md +225 -0
- package/node_modules/micromark-factory-title/dev/index.d.ts +36 -0
- package/node_modules/micromark-factory-title/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-factory-title/dev/index.js +169 -0
- package/node_modules/micromark-factory-title/index.d.ts +36 -0
- package/node_modules/micromark-factory-title/index.d.ts.map +1 -0
- package/node_modules/micromark-factory-title/index.js +158 -0
- package/node_modules/micromark-factory-title/license +22 -0
- package/node_modules/micromark-factory-title/package.json +58 -0
- package/node_modules/micromark-factory-title/readme.md +229 -0
- package/node_modules/micromark-factory-whitespace/dev/index.d.ts +22 -0
- package/node_modules/micromark-factory-whitespace/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-factory-whitespace/dev/index.js +53 -0
- package/node_modules/micromark-factory-whitespace/index.d.ts +22 -0
- package/node_modules/micromark-factory-whitespace/index.d.ts.map +1 -0
- package/node_modules/micromark-factory-whitespace/index.js +44 -0
- package/node_modules/micromark-factory-whitespace/license +22 -0
- package/node_modules/micromark-factory-whitespace/package.json +57 -0
- package/node_modules/micromark-factory-whitespace/readme.md +205 -0
- package/node_modules/micromark-util-character/dev/index.d.ts +195 -0
- package/node_modules/micromark-util-character/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-util-character/dev/index.js +252 -0
- package/node_modules/micromark-util-character/index.d.ts +195 -0
- package/node_modules/micromark-util-character/index.d.ts.map +1 -0
- package/node_modules/micromark-util-character/index.js +246 -0
- package/node_modules/micromark-util-character/license +22 -0
- package/node_modules/micromark-util-character/package.json +57 -0
- package/node_modules/micromark-util-character/readme.md +446 -0
- package/node_modules/micromark-util-chunked/dev/index.d.ts +41 -0
- package/node_modules/micromark-util-chunked/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-util-chunked/dev/index.js +89 -0
- package/node_modules/micromark-util-chunked/index.d.ts +41 -0
- package/node_modules/micromark-util-chunked/index.d.ts.map +1 -0
- package/node_modules/micromark-util-chunked/index.js +81 -0
- package/node_modules/micromark-util-chunked/license +22 -0
- package/node_modules/micromark-util-chunked/package.json +57 -0
- package/node_modules/micromark-util-chunked/readme.md +219 -0
- package/node_modules/micromark-util-classify-character/dev/index.d.ts +18 -0
- package/node_modules/micromark-util-classify-character/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-util-classify-character/dev/index.js +38 -0
- package/node_modules/micromark-util-classify-character/index.d.ts +18 -0
- package/node_modules/micromark-util-classify-character/index.d.ts.map +1 -0
- package/node_modules/micromark-util-classify-character/index.js +27 -0
- package/node_modules/micromark-util-classify-character/license +22 -0
- package/node_modules/micromark-util-classify-character/package.json +59 -0
- package/node_modules/micromark-util-classify-character/readme.md +205 -0
- package/node_modules/micromark-util-combine-extensions/index.d.ts +22 -0
- package/node_modules/micromark-util-combine-extensions/index.d.ts.map +1 -0
- package/node_modules/micromark-util-combine-extensions/index.js +143 -0
- package/node_modules/micromark-util-combine-extensions/license +22 -0
- package/node_modules/micromark-util-combine-extensions/package.json +52 -0
- package/node_modules/micromark-util-combine-extensions/readme.md +201 -0
- package/node_modules/micromark-util-decode-numeric-character-reference/dev/index.d.ts +16 -0
- package/node_modules/micromark-util-decode-numeric-character-reference/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-util-decode-numeric-character-reference/dev/index.js +42 -0
- package/node_modules/micromark-util-decode-numeric-character-reference/index.d.ts +16 -0
- package/node_modules/micromark-util-decode-numeric-character-reference/index.d.ts.map +1 -0
- package/node_modules/micromark-util-decode-numeric-character-reference/index.js +32 -0
- package/node_modules/micromark-util-decode-numeric-character-reference/license +22 -0
- package/node_modules/micromark-util-decode-numeric-character-reference/package.json +59 -0
- package/node_modules/micromark-util-decode-numeric-character-reference/readme.md +184 -0
- package/node_modules/micromark-util-decode-string/dev/index.d.ts +14 -0
- package/node_modules/micromark-util-decode-string/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-util-decode-string/dev/index.js +53 -0
- package/node_modules/micromark-util-decode-string/index.d.ts +14 -0
- package/node_modules/micromark-util-decode-string/index.d.ts.map +1 -0
- package/node_modules/micromark-util-decode-string/index.js +45 -0
- package/node_modules/micromark-util-decode-string/license +22 -0
- package/node_modules/micromark-util-decode-string/package.json +63 -0
- package/node_modules/micromark-util-decode-string/readme.md +179 -0
- package/node_modules/micromark-util-edit-map/dev/index.d.ts +78 -0
- package/node_modules/micromark-util-edit-map/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-util-edit-map/dev/index.js +237 -0
- package/node_modules/micromark-util-edit-map/index.d.ts +78 -0
- package/node_modules/micromark-util-edit-map/index.d.ts.map +1 -0
- package/node_modules/micromark-util-edit-map/index.js +225 -0
- package/node_modules/micromark-util-edit-map/license +22 -0
- package/node_modules/micromark-util-edit-map/package.json +48 -0
- package/node_modules/micromark-util-edit-map/readme.md +234 -0
- package/node_modules/micromark-util-encode/index.d.ts +14 -0
- package/node_modules/micromark-util-encode/index.d.ts.map +1 -0
- package/node_modules/micromark-util-encode/index.js +33 -0
- package/node_modules/micromark-util-encode/license +22 -0
- package/node_modules/micromark-util-encode/package.json +47 -0
- package/node_modules/micromark-util-encode/readme.md +176 -0
- package/node_modules/micromark-util-html-tag-name/index.d.ts +30 -0
- package/node_modules/micromark-util-html-tag-name/index.d.ts.map +1 -0
- package/node_modules/micromark-util-html-tag-name/index.js +93 -0
- package/node_modules/micromark-util-html-tag-name/license +22 -0
- package/node_modules/micromark-util-html-tag-name/package.json +47 -0
- package/node_modules/micromark-util-html-tag-name/readme.md +193 -0
- package/node_modules/micromark-util-normalize-identifier/dev/index.d.ts +21 -0
- package/node_modules/micromark-util-normalize-identifier/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-util-normalize-identifier/dev/index.js +38 -0
- package/node_modules/micromark-util-normalize-identifier/index.d.ts +21 -0
- package/node_modules/micromark-util-normalize-identifier/index.d.ts.map +1 -0
- package/node_modules/micromark-util-normalize-identifier/index.js +33 -0
- package/node_modules/micromark-util-normalize-identifier/license +22 -0
- package/node_modules/micromark-util-normalize-identifier/package.json +58 -0
- package/node_modules/micromark-util-normalize-identifier/readme.md +187 -0
- package/node_modules/micromark-util-resolve-all/index.d.ts +22 -0
- package/node_modules/micromark-util-resolve-all/index.d.ts.map +1 -0
- package/node_modules/micromark-util-resolve-all/index.js +32 -0
- package/node_modules/micromark-util-resolve-all/license +22 -0
- package/node_modules/micromark-util-resolve-all/package.json +48 -0
- package/node_modules/micromark-util-resolve-all/readme.md +238 -0
- package/node_modules/micromark-util-sanitize-uri/dev/index.d.ts +36 -0
- package/node_modules/micromark-util-sanitize-uri/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-util-sanitize-uri/dev/index.js +124 -0
- package/node_modules/micromark-util-sanitize-uri/index.d.ts +36 -0
- package/node_modules/micromark-util-sanitize-uri/index.d.ts.map +1 -0
- package/node_modules/micromark-util-sanitize-uri/index.js +107 -0
- package/node_modules/micromark-util-sanitize-uri/license +22 -0
- package/node_modules/micromark-util-sanitize-uri/package.json +59 -0
- package/node_modules/micromark-util-sanitize-uri/readme.md +214 -0
- package/node_modules/micromark-util-subtokenize/dev/index.d.ts +12 -0
- package/node_modules/micromark-util-subtokenize/dev/index.d.ts.map +1 -0
- package/node_modules/micromark-util-subtokenize/dev/index.js +284 -0
- package/node_modules/micromark-util-subtokenize/dev/lib/splice-buffer.d.ts +161 -0
- package/node_modules/micromark-util-subtokenize/dev/lib/splice-buffer.d.ts.map +1 -0
- package/node_modules/micromark-util-subtokenize/dev/lib/splice-buffer.js +288 -0
- package/node_modules/micromark-util-subtokenize/index.d.ts +12 -0
- package/node_modules/micromark-util-subtokenize/index.d.ts.map +1 -0
- package/node_modules/micromark-util-subtokenize/index.js +228 -0
- package/node_modules/micromark-util-subtokenize/lib/splice-buffer.d.ts +161 -0
- package/node_modules/micromark-util-subtokenize/lib/splice-buffer.d.ts.map +1 -0
- package/node_modules/micromark-util-subtokenize/lib/splice-buffer.js +251 -0
- package/node_modules/micromark-util-subtokenize/license +22 -0
- package/node_modules/micromark-util-subtokenize/package.json +60 -0
- package/node_modules/micromark-util-subtokenize/readme.md +181 -0
- package/node_modules/micromark-util-symbol/lib/codes.d.ts +139 -0
- package/node_modules/micromark-util-symbol/lib/codes.d.ts.map +1 -0
- package/node_modules/micromark-util-symbol/lib/codes.js +158 -0
- package/node_modules/micromark-util-symbol/lib/constants.d.ts +37 -0
- package/node_modules/micromark-util-symbol/lib/constants.d.ts.map +1 -0
- package/node_modules/micromark-util-symbol/lib/constants.js +44 -0
- package/node_modules/micromark-util-symbol/lib/default.d.ts +5 -0
- package/node_modules/micromark-util-symbol/lib/default.d.ts.map +1 -0
- package/node_modules/micromark-util-symbol/lib/default.js +4 -0
- package/node_modules/micromark-util-symbol/lib/types.d.ts +106 -0
- package/node_modules/micromark-util-symbol/lib/types.d.ts.map +1 -0
- package/node_modules/micromark-util-symbol/lib/types.js +453 -0
- package/node_modules/micromark-util-symbol/lib/values.d.ts +102 -0
- package/node_modules/micromark-util-symbol/lib/values.d.ts.map +1 -0
- package/node_modules/micromark-util-symbol/lib/values.js +109 -0
- package/node_modules/micromark-util-symbol/license +22 -0
- package/node_modules/micromark-util-symbol/package.json +43 -0
- package/node_modules/micromark-util-symbol/readme.md +168 -0
- package/node_modules/micromark-util-types/index.d.ts +1315 -0
- package/node_modules/micromark-util-types/index.js +2 -0
- package/node_modules/micromark-util-types/license +22 -0
- package/node_modules/micromark-util-types/package.json +37 -0
- package/node_modules/micromark-util-types/readme.md +151 -0
- package/node_modules/smol-toml/LICENSE +24 -0
- package/node_modules/smol-toml/README.md +605 -0
- package/node_modules/smol-toml/dist/date.d.ts +41 -0
- package/node_modules/smol-toml/dist/date.js +156 -0
- package/node_modules/smol-toml/dist/error.d.ts +37 -0
- package/node_modules/smol-toml/dist/error.js +67 -0
- package/node_modules/smol-toml/dist/extract.js +289 -0
- package/node_modules/smol-toml/dist/index.cjs +770 -0
- package/node_modules/smol-toml/dist/index.d.ts +44 -0
- package/node_modules/smol-toml/dist/index.js +34 -0
- package/node_modules/smol-toml/dist/parse.d.ts +73 -0
- package/node_modules/smol-toml/dist/parse.js +164 -0
- package/node_modules/smol-toml/dist/primitive.js +151 -0
- package/node_modules/smol-toml/dist/stringify.d.ts +32 -0
- package/node_modules/smol-toml/dist/stringify.js +219 -0
- package/node_modules/smol-toml/dist/struct.js +156 -0
- package/node_modules/smol-toml/dist/util.d.ts +43 -0
- package/node_modules/smol-toml/dist/util.js +57 -0
- package/node_modules/smol-toml/package.json +68 -0
- package/node_modules/unist-util-stringify-position/index.d.ts +1 -0
- package/node_modules/unist-util-stringify-position/index.js +1 -0
- package/node_modules/unist-util-stringify-position/lib/index.d.ts +61 -0
- package/node_modules/unist-util-stringify-position/lib/index.js +84 -0
- package/node_modules/unist-util-stringify-position/license +22 -0
- package/node_modules/unist-util-stringify-position/package.json +80 -0
- package/node_modules/unist-util-stringify-position/readme.md +206 -0
- package/package.json +27 -8
- package/scripts/prepare-github-keyring.js +29 -0
- package/src/codex-app.js +20 -2
- package/src/codex-pricing.js +79 -0
- package/src/codex-projects.js +90 -0
- package/src/codex-session-scanner.js +748 -0
- package/src/codex-session-usage.js +332 -0
- package/src/dashboard/app.js +2 -2
- package/src/dashboard/dist/app.js +135 -60
- package/src/dashboard/dist/app.js.map +1 -1
- package/src/dashboard/dist/styles.css +1 -1
- package/src/dashboard/react/components/AnimatedUsage.test.tsx +78 -0
- package/src/dashboard/react/components/CardStatusIcons.test.tsx +146 -0
- package/src/dashboard/react/components/CardStatusIcons.tsx +138 -0
- package/src/dashboard/react/components/ImageGallery.tsx +22 -0
- package/src/dashboard/react/components/ModelSettings.test.tsx +4 -4
- package/src/dashboard/react/components/ModelSettings.tsx +1 -1
- package/src/dashboard/react/components/OpenChatButton.test.tsx +19 -0
- package/src/dashboard/react/components/OpenChatButton.tsx +2 -1
- package/src/dashboard/react/components/PrGlyph.tsx +35 -0
- package/src/dashboard/react/components/PullRequestBadges.tsx +18 -0
- package/src/dashboard/react/components/QueuedPrompts.test.tsx +42 -0
- package/src/dashboard/react/components/QueuedPrompts.tsx +34 -0
- package/src/dashboard/react/components/RelativeTime.test.tsx +100 -0
- package/src/dashboard/react/components/RelativeTime.tsx +46 -11
- package/src/dashboard/react/components/ReplyCover.test.tsx +30 -0
- package/src/dashboard/react/components/ReplyCover.tsx +33 -0
- package/src/dashboard/react/components/ReplyImagePreview.test.tsx +90 -0
- package/src/dashboard/react/components/ReplyImagePreview.tsx +34 -0
- package/src/dashboard/react/components/ReplyMarkdown.test.tsx +108 -0
- package/src/dashboard/react/components/ReplyMarkdown.tsx +112 -0
- package/src/dashboard/react/components/SavedUsageFormat.test.tsx +11 -0
- package/src/dashboard/react/components/SavedUsageFormat.tsx +68 -0
- package/src/dashboard/react/components/StatusBadge.tsx +2 -2
- package/src/dashboard/react/components/TaskBoard.test.tsx +256 -1
- package/src/dashboard/react/components/TaskBoard.tsx +136 -28
- package/src/dashboard/react/components/TaskCard.tsx +7 -4
- package/src/dashboard/react/components/TaskDetail.test.tsx +92 -1
- package/src/dashboard/react/components/TaskDetail.tsx +37 -10
- package/src/dashboard/react/components/TurnContent.test.tsx +44 -0
- package/src/dashboard/react/components/TurnContent.tsx +53 -0
- package/src/dashboard/react/components/UsagePanel.tsx +2 -2
- package/src/dashboard/react/image-cache.test.ts +23 -0
- package/src/dashboard/react/image-cache.ts +33 -0
- package/src/dashboard/react/presentation.test.tsx +31 -0
- package/src/dashboard/react/styles.css +126 -2
- package/src/dashboard/react/types.ts +55 -1
- package/src/dashboard/time.js +39 -0
- package/src/dashboard.js +4 -3
- package/src/data/codex-pricing.json +537 -0
- package/src/mcp-app/dist/index.html +216 -112
- package/src/mcp-app/react/AppearancePicker.test.tsx +50 -0
- package/src/mcp-app/react/AppearancePicker.tsx +56 -0
- package/src/mcp-app/react/GitHubConnection.test.tsx +78 -0
- package/src/mcp-app/react/GitHubConnection.tsx +78 -0
- package/src/mcp-app/react/NextNotificationCenter.test.tsx +121 -0
- package/src/mcp-app/react/NextNotificationCenter.tsx +94 -0
- package/src/mcp-app/react/NextTaskDetail.test.tsx +124 -0
- package/src/mcp-app/react/NextTaskDetail.tsx +147 -0
- package/src/mcp-app/react/ProjectPicker.tsx +49 -0
- package/src/mcp-app/react/main.test.tsx +499 -49
- package/src/mcp-app/react/main.tsx +359 -86
- package/src/mcp-app/react/styles.css +186 -9
- package/src/mcp-app/react/themes.test.ts +34 -0
- package/src/mcp-app/react/themes.ts +112 -0
- package/src/mcp-app.js +238 -120
- package/src/mcp.js +8 -1
- package/src/next-cache.js +16 -0
- package/src/next-github.js +273 -0
- package/src/next-notifications.js +90 -0
- package/src/recorded-usage-tracker.js +92 -0
- package/src/reply-image.js +65 -0
- package/src/usage-tracker.js +7 -6
- package/src/usage.js +7 -355
- package/src/user-prompt.js +55 -0
- package/src/version.js +2 -1
- package/node_modules/proper-lockfile/CHANGELOG.md +0 -108
- package/node_modules/which/CHANGELOG.md +0 -166
- package/src/resolve-ccusage.js +0 -17
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { act, cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { MantineProvider } from "@mantine/core";
|
|
3
|
+
import { afterEach, beforeEach, expect, test, vi } from "vitest";
|
|
4
|
+
import { AppearancePicker } from "./AppearancePicker";
|
|
5
|
+
|
|
6
|
+
beforeEach(() => vi.stubGlobal("PointerEvent", class extends MouseEvent {
|
|
7
|
+
pointerType: string;
|
|
8
|
+
constructor(type: string, init: PointerEventInit = {}) { super(type, init); this.pointerType = init.pointerType ?? "mouse"; }
|
|
9
|
+
}));
|
|
10
|
+
afterEach(() => { cleanup(); vi.useRealTimers(); vi.unstubAllGlobals(); });
|
|
11
|
+
test("hover stays open while crossing into the panel and closes after leaving", () => {
|
|
12
|
+
vi.useFakeTimers();
|
|
13
|
+
render(<MantineProvider><AppearancePicker value="dark" onChange={vi.fn()} /></MantineProvider>);
|
|
14
|
+
const button = screen.getByRole("button", { name: "Appearance" });
|
|
15
|
+
fireEvent.pointerEnter(button, { pointerType: "mouse" });
|
|
16
|
+
const panel = screen.getByRole("region", { name: "Appearance settings" });
|
|
17
|
+
fireEvent.pointerLeave(button, { pointerType: "mouse" });
|
|
18
|
+
fireEvent.pointerEnter(panel, { pointerType: "mouse" });
|
|
19
|
+
act(() => vi.advanceTimersByTime(250));
|
|
20
|
+
expect(panel).toBeVisible();
|
|
21
|
+
fireEvent.pointerLeave(panel, { pointerType: "mouse" });
|
|
22
|
+
act(() => vi.advanceTimersByTime(250));
|
|
23
|
+
expect(screen.queryByRole("region", { name: "Appearance settings" })).not.toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
test("tap opens, appearance changes, and Escape restores focus", () => {
|
|
26
|
+
const onChange = vi.fn();
|
|
27
|
+
render(<MantineProvider><AppearancePicker value="dark" onChange={onChange} /></MantineProvider>);
|
|
28
|
+
const button = screen.getByRole("button", { name: "Appearance" });
|
|
29
|
+
fireEvent.pointerEnter(button, { pointerType: "touch" });
|
|
30
|
+
expect(screen.queryByRole("region", { name: "Appearance settings" })).not.toBeInTheDocument();
|
|
31
|
+
fireEvent.click(button);
|
|
32
|
+
fireEvent.click(screen.getByRole("radio", { name: "DAY" }));
|
|
33
|
+
expect(onChange).toHaveBeenCalledWith("light");
|
|
34
|
+
fireEvent.keyDown(document, { key: "Escape" });
|
|
35
|
+
expect(screen.queryByRole("region", { name: "Appearance settings" })).not.toBeInTheDocument();
|
|
36
|
+
expect(button).toHaveFocus();
|
|
37
|
+
});
|
|
38
|
+
test("theme rows use native variants and keyboard selection", () => {
|
|
39
|
+
const onVisualThemeChange = vi.fn();
|
|
40
|
+
render(<MantineProvider><AppearancePicker value="light" appearance="light" visualThemeId="chatgpt" onChange={vi.fn()} onVisualThemeChange={onVisualThemeChange} /></MantineProvider>);
|
|
41
|
+
fireEvent.click(screen.getByRole("button", { name: "Appearance" }));
|
|
42
|
+
expect(screen.queryByRole("radio", { name: "Dracula" })).not.toBeInTheDocument();
|
|
43
|
+
expect(screen.getByRole("radio", { name: "Proof" })).toBeVisible();
|
|
44
|
+
const selected = screen.getByRole("radio", { name: "ChatGPT" });
|
|
45
|
+
selected.focus();
|
|
46
|
+
fireEvent.keyDown(selected, { key: "ArrowDown" });
|
|
47
|
+
expect(screen.queryByRole("radio", { name: "Ayu" })).not.toBeInTheDocument();
|
|
48
|
+
expect(onVisualThemeChange).toHaveBeenCalledWith("absolutely");
|
|
49
|
+
expect(screen.getByRole("radio", { name: "Absolutely" })).toHaveFocus();
|
|
50
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { SegmentedControl } from "@mantine/core";
|
|
3
|
+
import { IconCheck, IconPalette } from "@tabler/icons-react";
|
|
4
|
+
|
|
5
|
+
import { VISUAL_THEMES, themeVariables, type Appearance } from "./themes";
|
|
6
|
+
export function AppearancePicker({ value, onChange, visualThemeId = "chatgpt", appearance = "dark", onVisualThemeChange }: { value: "light" | "dark" | "system"; onChange: (value: string) => void; visualThemeId?: string; appearance?: Appearance; onVisualThemeChange?: (id: string) => void }) {
|
|
7
|
+
const [opened, setOpened] = useState(false);
|
|
8
|
+
const root = useRef<HTMLDivElement>(null);
|
|
9
|
+
const button = useRef<HTMLButtonElement>(null);
|
|
10
|
+
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
11
|
+
const cancelClose = () => { if (timer.current !== null) clearTimeout(timer.current); timer.current = null; };
|
|
12
|
+
const close = () => { cancelClose(); setOpened(false); };
|
|
13
|
+
const open = () => { cancelClose(); setOpened(true); };
|
|
14
|
+
const enter = (event: React.PointerEvent) => { if (event.pointerType !== "touch") open(); };
|
|
15
|
+
const leave = (event: React.PointerEvent) => {
|
|
16
|
+
if (event.pointerType === "touch") return;
|
|
17
|
+
cancelClose();
|
|
18
|
+
timer.current = setTimeout(close, 200);
|
|
19
|
+
};
|
|
20
|
+
useEffect(() => cancelClose, []);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!opened) return;
|
|
23
|
+
const outside = (event: PointerEvent) => { if (!root.current?.contains(event.target as Node)) close(); };
|
|
24
|
+
const escape = (event: KeyboardEvent) => { if (event.key === "Escape") { close(); button.current?.focus(); } };
|
|
25
|
+
document.addEventListener("pointerdown", outside);
|
|
26
|
+
document.addEventListener("keydown", escape);
|
|
27
|
+
return () => { document.removeEventListener("pointerdown", outside); document.removeEventListener("keydown", escape); };
|
|
28
|
+
}, [opened]);
|
|
29
|
+
return <div className="next-appearance" ref={root}>
|
|
30
|
+
<button ref={button} className="next-icon-button" aria-label="Appearance" title="Appearance" aria-expanded={opened} aria-controls="next-appearance-panel" onPointerEnter={enter} onPointerLeave={leave} onClick={open}><IconPalette size={18} /></button>
|
|
31
|
+
{opened && <section id="next-appearance-panel" className="next-appearance-panel" aria-label="Appearance settings" onPointerEnter={enter} onPointerLeave={leave}>
|
|
32
|
+
<h2>Appearance</h2>
|
|
33
|
+
<SegmentedControl fullWidth aria-label="Light and dark appearance" value={value} onChange={onChange} data={[{ value: "light", label: "DAY" }, { value: "dark", label: "NIGHT" }, { value: "system", label: "SYSTEM" }]} size="xs" />
|
|
34
|
+
<h3>Theme</h3>
|
|
35
|
+
<div className="next-theme-list" role="radiogroup" aria-label="Theme" onKeyDown={event => {
|
|
36
|
+
if (!["ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
|
|
37
|
+
const buttons = Array.from(event.currentTarget.querySelectorAll<HTMLButtonElement>('button[role="radio"]'));
|
|
38
|
+
const current = buttons.indexOf(event.target as HTMLButtonElement);
|
|
39
|
+
if (current < 0) return;
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
const index = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (current + (event.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length;
|
|
42
|
+
buttons[index].focus(); buttons[index].click();
|
|
43
|
+
}}>
|
|
44
|
+
{VISUAL_THEMES.filter(item => item.variants[appearance]).sort((a, b) => a.id === "chatgpt" ? -1 : b.id === "chatgpt" ? 1 : a.label.localeCompare(b.label)).map((item, index) => {
|
|
45
|
+
const colors = themeVariables(item, appearance);
|
|
46
|
+
return <div key={item.id}>
|
|
47
|
+
{index === 1 && <div className="next-theme-group">Codex themes</div>}
|
|
48
|
+
<button role="radio" tabIndex={visualThemeId === item.id ? 0 : -1} aria-checked={visualThemeId === item.id} className="next-appearance-theme" onClick={() => onVisualThemeChange?.(item.id)}>
|
|
49
|
+
<span className="next-appearance-preview" style={{ background: colors["--taskchef-surface"], color: colors["--taskchef-link"], borderColor: colors["--taskchef-border-heavy"], fontFamily: colors["--taskchef-font"] }} aria-hidden="true">Aa</span><span>{item.label}</span>{visualThemeId === item.id && <IconCheck size={16} aria-hidden="true" />}
|
|
50
|
+
</button>
|
|
51
|
+
</div>;
|
|
52
|
+
})}
|
|
53
|
+
</div>
|
|
54
|
+
</section>}
|
|
55
|
+
</div>;
|
|
56
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { MantineProvider } from "@mantine/core";
|
|
2
|
+
import { act, cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import { afterEach, expect, test, vi } from "vitest";
|
|
4
|
+
import { GitHubConnection } from "./GitHubConnection";
|
|
5
|
+
|
|
6
|
+
afterEach(() => { cleanup(); vi.useRealTimers(); vi.restoreAllMocks(); });
|
|
7
|
+
const auth = { configured: true, connected: false, login: null };
|
|
8
|
+
function setup(props: Partial<Parameters<typeof GitHubConnection>[0]> = {}) {
|
|
9
|
+
const request = vi.fn().mockResolvedValue({ ...auth, pending: { userCode: "ABCD-EFGH", verificationUrl: "https://github.com/login/device", expiresAt: Date.now() + 900_000 } });
|
|
10
|
+
const openLink = vi.fn().mockResolvedValue({});
|
|
11
|
+
const refresh = vi.fn().mockResolvedValue({});
|
|
12
|
+
const openSettings = vi.fn().mockResolvedValue({});
|
|
13
|
+
render(<MantineProvider><GitHubConnection auth={auth} request={request} openLink={openLink} refresh={refresh} openSettings={openSettings} {...props} /></MantineProvider>);
|
|
14
|
+
fireEvent.click(screen.getByRole("button", { name: "GitHub connection" }));
|
|
15
|
+
return { request, openLink, refresh, openSettings };
|
|
16
|
+
}
|
|
17
|
+
test("unconfigured connection directs users to native plugin settings", async () => {
|
|
18
|
+
const c = setup({ auth: { ...auth, configured: false } });
|
|
19
|
+
fireEvent.click(await screen.findByRole("button", { name: "Open plugin settings" }));
|
|
20
|
+
await waitFor(() => expect(c.openSettings).toHaveBeenCalledOnce());
|
|
21
|
+
});
|
|
22
|
+
test("sign-in displays only the user code and opens GitHub's verification page", async () => {
|
|
23
|
+
const c = setup();
|
|
24
|
+
expect(await screen.findByText(/read PR and CI status for your selected repositories/)).toBeInTheDocument();
|
|
25
|
+
expect(screen.getByRole("button", { name: "Repository access" })).toHaveAttribute("data-variant", "default");
|
|
26
|
+
fireEvent.click(await screen.findByRole("button", { name: "Sign in with GitHub" }));
|
|
27
|
+
expect(await screen.findByText("ABCD-EFGH")).toBeInTheDocument();
|
|
28
|
+
expect(c.request).toHaveBeenCalledWith("start");
|
|
29
|
+
fireEvent.click(screen.getByRole("button", { name: "Open GitHub" }));
|
|
30
|
+
await waitFor(() => expect(c.openLink).toHaveBeenCalledWith("https://github.com/login/device"));
|
|
31
|
+
});
|
|
32
|
+
test("connected account can remove its local sign-in and refresh the board", async () => {
|
|
33
|
+
const request = vi.fn().mockResolvedValue(auth);
|
|
34
|
+
const c = setup({ auth: { ...auth, connected: true, login: "tester" }, request });
|
|
35
|
+
expect(await screen.findByText(/Install TaskChef in each organization/)).toBeInTheDocument();
|
|
36
|
+
expect(screen.queryByText(/cached for one minute/)).not.toBeInTheDocument();
|
|
37
|
+
expect(screen.getByRole("button", { name: "Manage repository access" })).toHaveAttribute("data-variant", "default");
|
|
38
|
+
expect(await screen.findByText("tester")).toBeInTheDocument();
|
|
39
|
+
fireEvent.click(screen.getByRole("button", { name: "Disconnect this computer" }));
|
|
40
|
+
await waitFor(() => expect(request).toHaveBeenCalledWith("disconnect"));
|
|
41
|
+
await waitFor(() => expect(c.refresh).toHaveBeenCalledOnce());
|
|
42
|
+
expect(await screen.findByRole("button", { name: "Sign in with GitHub" })).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("five-second board snapshots do not reset the pending sign-in timer", async () => {
|
|
46
|
+
vi.useFakeTimers();
|
|
47
|
+
const pending = { userCode: "ABCD-EFGH", verificationUrl: "https://github.com/login/device", expiresAt: Date.now() + 900_000 };
|
|
48
|
+
const request = vi.fn().mockResolvedValue({ ...auth, pending });
|
|
49
|
+
const refresh = vi.fn().mockResolvedValue({});
|
|
50
|
+
const props = { request, refresh, openSettings: vi.fn(), openLink: vi.fn() };
|
|
51
|
+
const { rerender } = render(<MantineProvider><GitHubConnection {...props} auth={{ ...auth, pending }} /></MantineProvider>);
|
|
52
|
+
fireEvent.click(screen.getByRole("button", { name: "GitHub connection" }));
|
|
53
|
+
await act(async () => { vi.advanceTimersByTime(4000); });
|
|
54
|
+
rerender(<MantineProvider><GitHubConnection {...props} auth={{ ...auth, pending: { ...pending } }} /></MantineProvider>);
|
|
55
|
+
await act(async () => { vi.advanceTimersByTime(1000); });
|
|
56
|
+
expect(request).toHaveBeenCalledWith("poll");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
test("pending sign-in mounted hidden resumes polling after the panel becomes visible", async () => {
|
|
61
|
+
vi.useFakeTimers();
|
|
62
|
+
let visibility: DocumentVisibilityState = "hidden";
|
|
63
|
+
vi.spyOn(document, "visibilityState", "get").mockImplementation(() => visibility);
|
|
64
|
+
const pending = {userCode:"ABCD-EFGH",verificationUrl:"https://github.com/login/device",expiresAt:Date.now()+900000};
|
|
65
|
+
const request = vi.fn().mockResolvedValue({...auth,connected:true,login:"tester"});
|
|
66
|
+
const refresh = vi.fn().mockResolvedValue({});
|
|
67
|
+
render(<MantineProvider><GitHubConnection inline auth={{...auth,pending}} request={request} refresh={refresh} openSettings={vi.fn()} openLink={vi.fn()} /></MantineProvider>);
|
|
68
|
+
await act(async () => { vi.advanceTimersByTime(10000); });
|
|
69
|
+
expect(request).not.toHaveBeenCalled();
|
|
70
|
+
visibility = "visible";
|
|
71
|
+
fireEvent(document,new Event("visibilitychange"));
|
|
72
|
+
await act(async () => { vi.advanceTimersByTime(5000); });
|
|
73
|
+
expect(request).toHaveBeenCalledWith("poll");
|
|
74
|
+
expect(refresh).toHaveBeenCalledOnce();
|
|
75
|
+
visibility = "hidden";
|
|
76
|
+
await act(async () => { vi.advanceTimersByTime(10000); });
|
|
77
|
+
expect(request).toHaveBeenCalledTimes(1);
|
|
78
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ActionIcon, Alert, Button, Group, Modal, Stack, Text } from "@mantine/core";
|
|
2
|
+
import { IconBrandGithub } from "@tabler/icons-react";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
export interface GitHubAuth {
|
|
6
|
+
configured: boolean;
|
|
7
|
+
connected: boolean;
|
|
8
|
+
login: string | null;
|
|
9
|
+
error?: string;
|
|
10
|
+
pending?: { userCode: string; verificationUrl: string; expiresAt: number };
|
|
11
|
+
}
|
|
12
|
+
export function GitHubConnection({ auth, request, openLink, refresh, openSettings, openSignal = 0, inline = false, hideTrigger = false }: {
|
|
13
|
+
openSignal?: number;
|
|
14
|
+
inline?: boolean;
|
|
15
|
+
hideTrigger?: boolean;
|
|
16
|
+
auth: GitHubAuth;
|
|
17
|
+
request: (action: "status" | "start" | "poll" | "disconnect") => Promise<GitHubAuth>;
|
|
18
|
+
openLink: (url: string) => Promise<unknown>;
|
|
19
|
+
refresh: () => Promise<unknown>;
|
|
20
|
+
openSettings: () => Promise<unknown>;
|
|
21
|
+
}) {
|
|
22
|
+
const [opened, setOpened] = useState(inline);
|
|
23
|
+
const [current, setCurrent] = useState(auth);
|
|
24
|
+
const [busy, setBusy] = useState(false);
|
|
25
|
+
const [error, setError] = useState<string | null>(null);
|
|
26
|
+
useEffect(() => { setCurrent(auth); }, [auth]);
|
|
27
|
+
useEffect(() => { if (openSignal > 0) { setError(null); setOpened(true); } }, [openSignal]);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!opened || !current.pending) return;
|
|
30
|
+
let cancelled = false;
|
|
31
|
+
let polling = false;
|
|
32
|
+
const timer = window.setInterval(async () => {
|
|
33
|
+
if (polling || document.visibilityState === "hidden") return;
|
|
34
|
+
polling = true;
|
|
35
|
+
try {
|
|
36
|
+
const next = await request("poll");
|
|
37
|
+
if (cancelled) return;
|
|
38
|
+
setCurrent(next);
|
|
39
|
+
if (next.connected) await refresh();
|
|
40
|
+
} catch (cause) { if (!cancelled) { setError(String(cause)); setCurrent((value) => ({ ...value, pending: undefined })); } }
|
|
41
|
+
finally { polling = false; }
|
|
42
|
+
}, 5000);
|
|
43
|
+
return () => { cancelled = true; window.clearInterval(timer); };
|
|
44
|
+
// Snapshot polling supplies a new pending object; the code itself stays stable.
|
|
45
|
+
}, [opened, current.pending?.userCode, request, refresh]);
|
|
46
|
+
async function action(operation: () => Promise<unknown>) {
|
|
47
|
+
setBusy(true); setError(null);
|
|
48
|
+
try { await operation(); } catch (cause) { setError(String(cause)); }
|
|
49
|
+
finally { setBusy(false); }
|
|
50
|
+
}
|
|
51
|
+
const controls = <Stack gap="sm">
|
|
52
|
+
{(error || current.error) && <Alert color="red" role="alert">{error || current.error}</Alert>}
|
|
53
|
+
{!current.configured ? <>
|
|
54
|
+
<Text size="sm">GitHub connection is not ready. Open plugin settings and try again.</Text>
|
|
55
|
+
<Button onClick={() => void action(openSettings)}>Open plugin settings</Button>
|
|
56
|
+
</> : current.connected ? <>
|
|
57
|
+
<Text size="sm">Connected as <strong>{current.login}</strong>.</Text>
|
|
58
|
+
<Text size="sm">Install TaskChef in each organization you want to use, and select the repositories it can access. An organization owner may need to approve the installation.</Text>
|
|
59
|
+
<Button variant="default" onClick={() => void action(() => openLink("https://github.com/settings/installations"))}>Manage repository access</Button>
|
|
60
|
+
<Button disabled={busy} variant="default" onClick={() => void action(async () => { setCurrent(await request("disconnect")); await refresh(); })}>Disconnect this computer</Button>
|
|
61
|
+
<Text c="dimmed" size="xs">You can reconnect at any time.</Text>
|
|
62
|
+
</> : current.pending ? <>
|
|
63
|
+
<Text size="sm">Enter this code on GitHub:</Text>
|
|
64
|
+
<Text fw={700} size="xl" style={{ letterSpacing: "0.1em" }}>{current.pending.userCode}</Text>
|
|
65
|
+
<Button disabled={busy} onClick={() => void action(() => openLink(current.pending!.verificationUrl))}>Open GitHub</Button>
|
|
66
|
+
<Text c="dimmed" size="xs">Waiting for your approval. Keep this dialog open. The code expires in 15 minutes.</Text>
|
|
67
|
+
</> : <>
|
|
68
|
+
<Text size="sm">Let TaskChef read PR and CI status for your selected repositories and update the board automatically.</Text>
|
|
69
|
+
<Group><Button disabled={busy} onClick={() => void action(async () => setCurrent(await request("start")))}>Sign in with GitHub</Button>
|
|
70
|
+
<Button variant="default" onClick={() => void action(() => openLink("https://github.com/settings/installations"))}>Repository access</Button></Group>
|
|
71
|
+
</>}
|
|
72
|
+
</Stack>;
|
|
73
|
+
if (inline) return controls;
|
|
74
|
+
return <>
|
|
75
|
+
{!hideTrigger && <ActionIcon aria-label="GitHub connection" title={auth.connected ? `GitHub: ${auth.login}` : "Connect GitHub"} variant="subtle" onClick={() => { setError(null); setOpened(true); }}><IconBrandGithub size={17} /></ActionIcon>}
|
|
76
|
+
<Modal opened={opened} onClose={() => setOpened(false)} title="GitHub PR status" centered>{controls}</Modal>
|
|
77
|
+
</>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { act, cleanup, fireEvent, render, screen, within } from "@testing-library/react";
|
|
2
|
+
import { afterEach, expect, test, vi } from "vitest";
|
|
3
|
+
import { NextNotificationCenter, type NextNotification } from "./NextNotificationCenter";
|
|
4
|
+
|
|
5
|
+
afterEach(cleanup);
|
|
6
|
+
const notice = (id: string, extra: Partial<NextNotification> = {}): NextNotification => ({
|
|
7
|
+
id, taskId: "chat", title: "Fix login is ready for input or review", detail: "", kind: "ready", read: false,
|
|
8
|
+
timestamp: "2026-10-09T07:00:00Z", ...extra,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("existing repeated chat messages keep the latest timestamp and read state", () => {
|
|
12
|
+
const onAction = vi.fn().mockResolvedValue(undefined);
|
|
13
|
+
const onOpen = vi.fn();
|
|
14
|
+
const latest = notice("latest", { timestamp: "2026-10-09T08:00:00Z", read: true });
|
|
15
|
+
render(<NextNotificationCenter state={{ revision: 1, items: [latest, notice("old"), notice("older")] }} toasts={[]} onAction={onAction} onOpen={onOpen} onDismiss={vi.fn()} />);
|
|
16
|
+
expect(screen.getByRole("button", { name: "Notifications" })).toBeVisible();
|
|
17
|
+
fireEvent.click(screen.getByRole("button", { name: "Notifications" }));
|
|
18
|
+
const center = screen.getByRole("region", { name: "Notification center" });
|
|
19
|
+
expect(within(center).getAllByRole("article")).toHaveLength(1);
|
|
20
|
+
expect(center.querySelector("time")).toHaveAttribute("datetime", latest.timestamp);
|
|
21
|
+
fireEvent.click(within(center).getByRole("button", { name: "Unread" }));
|
|
22
|
+
expect(within(center).getByText("No unread notifications")).toBeVisible();
|
|
23
|
+
fireEvent.click(within(center).getByRole("button", { name: "All" }));
|
|
24
|
+
fireEvent.click(within(center).getByRole("button", { name: /Fix login is ready/ }));
|
|
25
|
+
expect(onAction).toHaveBeenCalledWith("read", "latest");
|
|
26
|
+
expect(onOpen).toHaveBeenCalledWith(latest);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("new repeats replace the visible entry while other chats and messages stay separate", () => {
|
|
30
|
+
const props = { toasts: [], onAction: vi.fn().mockResolvedValue(undefined), onOpen: vi.fn(), onDismiss: vi.fn() };
|
|
31
|
+
const original = notice("old");
|
|
32
|
+
const separate = [notice("other-chat", { taskId: "another" }), notice("interrupted", { kind: "interrupted", title: "Fix login was interrupted" }), notice("detail", { detail: "Different details" }), notice("global-one", { taskId: null }), notice("global-two", { taskId: null })];
|
|
33
|
+
const { rerender } = render(<NextNotificationCenter {...props} state={{ revision: 1, items: [original, ...separate] }} />);
|
|
34
|
+
fireEvent.click(screen.getByRole("button", { name: "Notifications, unread" }));
|
|
35
|
+
const latest = notice("latest", { timestamp: "2026-10-09T08:00:00Z" });
|
|
36
|
+
rerender(<NextNotificationCenter {...props} state={{ revision: 2, items: [latest, original, ...separate] }} />);
|
|
37
|
+
const center = screen.getByRole("region", { name: "Notification center" });
|
|
38
|
+
const entries = within(center).getAllByRole("article");
|
|
39
|
+
expect(entries).toHaveLength(6);
|
|
40
|
+
expect(entries[0].querySelector("time")).toHaveAttribute("datetime", latest.timestamp);
|
|
41
|
+
expect(within(center).getByRole("button", { name: "Unread (6)" })).toBeVisible();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
test("opening and scrolling marks only notifications observed in the panel read", async () => {
|
|
46
|
+
const observers: Array<{ callback: IntersectionObserverCallback; observe: ReturnType<typeof vi.fn>; disconnect: ReturnType<typeof vi.fn> }> = [];
|
|
47
|
+
vi.stubGlobal("IntersectionObserver", class {
|
|
48
|
+
observe = vi.fn(); disconnect = vi.fn();
|
|
49
|
+
constructor(callback: IntersectionObserverCallback, options: IntersectionObserverInit) { expect(options.threshold).toBe(0.01); observers.push({ callback, observe: this.observe, disconnect: this.disconnect }); }
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
const onAction = vi.fn().mockResolvedValue(undefined);
|
|
53
|
+
const props = { toasts: [], onAction, onOpen: vi.fn(), onDismiss: vi.fn() };
|
|
54
|
+
render(<NextNotificationCenter {...props} state={{ revision: 1, items: [notice("visible"), notice("offscreen", { taskId: "other" })] }} />);
|
|
55
|
+
expect(onAction).not.toHaveBeenCalled();
|
|
56
|
+
fireEvent.click(screen.getByRole("button", { name: "Notifications, unread" }));
|
|
57
|
+
const entries = screen.getAllByRole("article");
|
|
58
|
+
await act(async () => observers[0].callback([{ target: entries[0], isIntersecting: true }, { target: entries[1], isIntersecting: false }] as unknown as IntersectionObserverEntry[], {} as IntersectionObserver));
|
|
59
|
+
expect(onAction).toHaveBeenCalledExactlyOnceWith("read", "visible");
|
|
60
|
+
await act(async () => observers[0].callback([{ target: entries[0], isIntersecting: true }, { target: entries[1], isIntersecting: true }] as unknown as IntersectionObserverEntry[], {} as IntersectionObserver));
|
|
61
|
+
expect(onAction).toHaveBeenCalledTimes(2);
|
|
62
|
+
expect(onAction).toHaveBeenLastCalledWith("read", "offscreen");
|
|
63
|
+
fireEvent.click(screen.getByRole("button", { name: "Close notifications" }));
|
|
64
|
+
expect(observers[0].disconnect).toHaveBeenCalled();
|
|
65
|
+
} finally { vi.unstubAllGlobals(); }
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
test("failed auto-read requests retry when the panel opens again", async () => {
|
|
70
|
+
const observers: IntersectionObserverCallback[] = [];
|
|
71
|
+
vi.stubGlobal("IntersectionObserver", class {
|
|
72
|
+
observe = vi.fn(); disconnect = vi.fn();
|
|
73
|
+
constructor(callback: IntersectionObserverCallback) { observers.push(callback); }
|
|
74
|
+
});
|
|
75
|
+
try {
|
|
76
|
+
const onAction = vi.fn().mockRejectedValueOnce(new Error("Temporary failure")).mockResolvedValue(undefined);
|
|
77
|
+
render(<NextNotificationCenter state={{revision: 1, items: [notice("retry")]}} toasts={[]} onAction={onAction} onOpen={vi.fn()} onDismiss={vi.fn()} />);
|
|
78
|
+
fireEvent.click(screen.getByRole("button", {name: "Notifications, unread"}));
|
|
79
|
+
await act(async () => observers[0]([{target: screen.getByRole("article"), isIntersecting: true}] as unknown as IntersectionObserverEntry[], {} as IntersectionObserver));
|
|
80
|
+
fireEvent.click(screen.getByRole("button", {name: "Close notifications"}));
|
|
81
|
+
fireEvent.click(screen.getByRole("button", {name: "Notifications, unread"}));
|
|
82
|
+
await act(async () => observers[1]([{target: screen.getByRole("article"), isIntersecting: true}] as unknown as IntersectionObserverEntry[], {} as IntersectionObserver));
|
|
83
|
+
expect(onAction).toHaveBeenCalledTimes(2);
|
|
84
|
+
} finally {vi.unstubAllGlobals();}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
test("hover opens the center, crossing to the panel keeps it open, and leaving closes it", () => {
|
|
89
|
+
vi.useFakeTimers();
|
|
90
|
+
try {
|
|
91
|
+
render(<NextNotificationCenter state={{revision: 1, items: []}} toasts={[]} onAction={vi.fn()} onOpen={vi.fn()} onDismiss={vi.fn()} />);
|
|
92
|
+
const bell = screen.getByRole("button", {name: "Notifications"});
|
|
93
|
+
fireEvent.pointerEnter(bell, {pointerType: "mouse"});
|
|
94
|
+
const panel = screen.getByRole("region", {name: "Notification center"});
|
|
95
|
+
fireEvent.pointerLeave(bell, {pointerType: "mouse"});
|
|
96
|
+
fireEvent.pointerEnter(panel, {pointerType: "mouse"});
|
|
97
|
+
act(() => vi.advanceTimersByTime(250));
|
|
98
|
+
expect(panel).toBeVisible();
|
|
99
|
+
fireEvent.click(bell);
|
|
100
|
+
expect(panel).toBeVisible();
|
|
101
|
+
fireEvent.pointerLeave(panel, {pointerType: "mouse"});
|
|
102
|
+
act(() => vi.advanceTimersByTime(250));
|
|
103
|
+
expect(screen.queryByRole("region", {name: "Notification center"})).not.toBeInTheDocument();
|
|
104
|
+
} finally {vi.useRealTimers();}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
test("Escape cancels a pending hover close before keyboard reopening", () => {
|
|
109
|
+
vi.useFakeTimers();
|
|
110
|
+
try {
|
|
111
|
+
render(<NextNotificationCenter state={{revision: 1, items: []}} toasts={[]} onAction={vi.fn()} onOpen={vi.fn()} onDismiss={vi.fn()} />);
|
|
112
|
+
const bell = screen.getByRole("button", {name: "Notifications"});
|
|
113
|
+
fireEvent.pointerEnter(bell, {pointerType: "mouse"});
|
|
114
|
+
fireEvent.pointerLeave(screen.getByRole("region", {name: "Notification center"}), {pointerType: "mouse"});
|
|
115
|
+
fireEvent.keyDown(document, {key: "Escape"});
|
|
116
|
+
expect(screen.queryByRole("region", {name: "Notification center"})).not.toBeInTheDocument();
|
|
117
|
+
fireEvent.click(bell);
|
|
118
|
+
act(() => vi.advanceTimersByTime(250));
|
|
119
|
+
expect(screen.getByRole("region", {name: "Notification center"})).toBeVisible();
|
|
120
|
+
} finally {vi.useRealTimers();}
|
|
121
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { IconBell, IconX } from "@tabler/icons-react";
|
|
3
|
+
|
|
4
|
+
export interface NextNotification { id: string; taskId: string | null; title: string; detail: string; kind: "ready" | "interrupted" | "confirmation" | "error"; read: boolean; timestamp: string; }
|
|
5
|
+
export interface NextNotificationState { revision: number; items: NextNotification[]; }
|
|
6
|
+
|
|
7
|
+
// Saved notifications arrive newest first. Keep the latest copy of each chat message.
|
|
8
|
+
function latestChatMessages(items: NextNotification[]) {
|
|
9
|
+
const seen = new Set<string>();
|
|
10
|
+
return items.filter((item) => {
|
|
11
|
+
if (!item.taskId) return true;
|
|
12
|
+
const key = JSON.stringify([item.taskId, item.kind, item.title, item.detail]);
|
|
13
|
+
if (seen.has(key)) return false;
|
|
14
|
+
seen.add(key);
|
|
15
|
+
return true;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function NextNotificationCenter({ state, toasts, onAction, onOpen, onDismiss }: {
|
|
19
|
+
state: NextNotificationState; toasts: NextNotification[];
|
|
20
|
+
onAction: (action: "read" | "read_all" | "clear", id?: string) => Promise<void>;
|
|
21
|
+
onOpen: (item: NextNotification) => void; onDismiss: (id: string) => void;
|
|
22
|
+
}) {
|
|
23
|
+
const [opened, setOpened] = useState(false);
|
|
24
|
+
const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
25
|
+
const cancelClose = () => { if (closeTimer.current !== null) clearTimeout(closeTimer.current); closeTimer.current = null; };
|
|
26
|
+
const closeCenter = () => { cancelClose(); setOpened(false); };
|
|
27
|
+
const openOnHover = (event: React.PointerEvent) => { if (event.pointerType === "touch") return; cancelClose(); setOpened(true); };
|
|
28
|
+
const closeAfterHover = (event: React.PointerEvent) => { if (event.pointerType === "touch") return; cancelClose(); closeTimer.current = setTimeout(() => { setOpened(false); closeTimer.current = null; }, 200); };
|
|
29
|
+
useEffect(() => () => cancelClose(), []);
|
|
30
|
+
const [filter, setFilter] = useState<"all" | "unread">("all");
|
|
31
|
+
const [busy, setBusy] = useState(false);
|
|
32
|
+
const root = useRef<HTMLDivElement>(null);
|
|
33
|
+
const bell = useRef<HTMLButtonElement>(null);
|
|
34
|
+
const list = useRef<HTMLDivElement>(null);
|
|
35
|
+
const reading = useRef(new Set<string>());
|
|
36
|
+
const actionRef = useRef(onAction);
|
|
37
|
+
actionRef.current = onAction;
|
|
38
|
+
const latest = latestChatMessages(state.items);
|
|
39
|
+
const unread = latest.filter((item) => !item.read).length;
|
|
40
|
+
const items = latest.filter((item) => filter === "all" || !item.read);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!opened) return;
|
|
43
|
+
const close = (event: PointerEvent) => { if (!root.current?.contains(event.target as Node)) closeCenter(); };
|
|
44
|
+
const escape = (event: KeyboardEvent) => { if (event.key === "Escape") { closeCenter(); bell.current?.focus(); } };
|
|
45
|
+
document.addEventListener("pointerdown", close);
|
|
46
|
+
document.addEventListener("keydown", escape);
|
|
47
|
+
return () => { document.removeEventListener("pointerdown", close); document.removeEventListener("keydown", escape); };
|
|
48
|
+
}, [opened]);
|
|
49
|
+
const unreadKey = JSON.stringify(items.filter(item => !item.read).map(item => item.id));
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!opened || !list.current || typeof IntersectionObserver === "undefined") return;
|
|
52
|
+
const observer = new IntersectionObserver(entries => {
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
const id = (entry.target as HTMLElement).dataset.notificationId;
|
|
55
|
+
if (!entry.isIntersecting || !id || reading.current.has(id)) continue;
|
|
56
|
+
reading.current.add(id);
|
|
57
|
+
void actionRef.current("read", id).catch(() => reading.current.delete(id));
|
|
58
|
+
}
|
|
59
|
+
}, { root: list.current, threshold: 0.01 });
|
|
60
|
+
for (const item of list.current.querySelectorAll("[data-notification-id][data-unread=true]")) observer.observe(item);
|
|
61
|
+
return () => observer.disconnect();
|
|
62
|
+
}, [opened, unreadKey]);
|
|
63
|
+
async function mutate(action: "read" | "read_all" | "clear", id?: string) {
|
|
64
|
+
setBusy(true);
|
|
65
|
+
try { await onAction(action, id); } catch { /* The caller displays the error. */ } finally { setBusy(false); }
|
|
66
|
+
}
|
|
67
|
+
return <div className="next-notifications" ref={root}>
|
|
68
|
+
<button ref={bell} className="next-icon-button" aria-label={unread > 0 ? "Notifications, unread" : "Notifications"} aria-expanded={opened} aria-controls="next-notification-panel" onPointerEnter={openOnHover} onPointerLeave={closeAfterHover} onClick={() => { cancelClose(); setOpened(true); }} title={unread ? "Unread notifications" : "Notifications"}>
|
|
69
|
+
<IconBell size={18} />{unread > 0 && <span className="next-unread-dot" aria-hidden="true" />}
|
|
70
|
+
</button>
|
|
71
|
+
{opened && <section onPointerEnter={openOnHover} onPointerLeave={closeAfterHover} id="next-notification-panel" className="next-notification-panel" aria-label="Notification center">
|
|
72
|
+
<div className="next-notification-heading"><strong>Notifications</strong><button className="next-icon-button" aria-label="Close notifications" onClick={() => { closeCenter(); bell.current?.focus(); }}><IconX size={16} /></button></div>
|
|
73
|
+
<div className="next-notification-controls">
|
|
74
|
+
<div role="group" aria-label="Notification filter"><button aria-pressed={filter === "all"} onClick={() => setFilter("all")}>All</button><button aria-pressed={filter === "unread"} onClick={() => setFilter("unread")}>Unread{unread > 0 ? ` (${unread})` : ""}</button></div>
|
|
75
|
+
<button disabled={busy || unread === 0} onClick={() => void mutate("read_all")}>Mark all read</button>
|
|
76
|
+
<button disabled={busy || state.items.length === 0} onClick={() => void mutate("clear")}>Clear all</button>
|
|
77
|
+
</div>
|
|
78
|
+
<div className="next-notification-list" ref={list}>
|
|
79
|
+
{items.length === 0 && <p className="next-notification-empty">{filter === "unread" ? "No unread notifications" : "No notifications yet"}</p>}
|
|
80
|
+
{items.map((item) => <article key={item.id} data-notification-id={item.id} data-unread={!item.read} className={`next-notification-item${item.read ? "" : " next-notification-unread"}`}>
|
|
81
|
+
<button className="next-notification-message" disabled={busy} onClick={() => { void mutate("read", item.id); if (item.taskId) { closeCenter(); onOpen(item); } }}>
|
|
82
|
+
<strong>{item.title}</strong>{item.detail && <span>{item.detail}</span>}<time dateTime={item.timestamp}>{new Date(item.timestamp).toLocaleString([], { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" })}</time>
|
|
83
|
+
</button>
|
|
84
|
+
</article>)}
|
|
85
|
+
</div>
|
|
86
|
+
</section>}
|
|
87
|
+
<div className="next-toast-stack" aria-live="polite" aria-atomic="false">
|
|
88
|
+
{toasts.map((item) => <article key={item.id} className={`next-toast next-toast-${item.kind}`}>
|
|
89
|
+
<button className="next-toast-message" onClick={() => { if (item.taskId) onOpen(item); void mutate("read", item.id); onDismiss(item.id); }}><strong>{item.title}</strong>{item.detail && <span>{item.detail}</span>}</button>
|
|
90
|
+
<button className="next-icon-button" aria-label={`Dismiss ${item.title}`} onClick={() => onDismiss(item.id)}><IconX size={16} /></button>
|
|
91
|
+
</article>)}
|
|
92
|
+
</div>
|
|
93
|
+
</div>;
|
|
94
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { MantineProvider } from "@mantine/core";
|
|
2
|
+
import { cleanup, fireEvent, render, screen, within } from "@testing-library/react";
|
|
3
|
+
import { afterEach, expect, test, vi } from "vitest";
|
|
4
|
+
import { fixtureTask } from "../../dashboard/react/fixtures";
|
|
5
|
+
import { NextTaskDetail } from "./NextTaskDetail";
|
|
6
|
+
|
|
7
|
+
afterEach(cleanup);
|
|
8
|
+
test("details show ordered PRs, one access alert per repo, and useful collapsed fields", () => {
|
|
9
|
+
const task = { ...fixtureTask(), replyExcerpt: "The **update** is ready.", relatedGitHubLinks: [], inputSource: "ordinary" as const,
|
|
10
|
+
observed: { archive: false, source: "vscode", lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false, latestTurnDurationMs: 480000, recordedChatDurationMs: 1200000, directChildCount: 155 },
|
|
11
|
+
detailPullRequests: [
|
|
12
|
+
{ url: "https://github.com/example/repo/pull/3", title: "Latest change", state: "open" as const, checks: "passed" as const },
|
|
13
|
+
{ url: "https://github.com/example/repo/pull/2", title: "Older change", state: "unknown" as const, checks: "unknown" as const, accessIssue: "denied" as const },
|
|
14
|
+
{ url: "https://github.com/example/other/pull/1", title: "Other repo", state: "unknown" as const, checks: "unknown" as const, accessIssue: "denied" as const },
|
|
15
|
+
] };
|
|
16
|
+
render(<MantineProvider><NextTaskDetail task={task} opened busy={false} error={null} onClose={() => {}} onOpenCodex={() => {}} /></MantineProvider>);
|
|
17
|
+
const prs = screen.getByRole("region", { name: "Pull requests" });
|
|
18
|
+
expect(within(prs).getAllByRole("link").map(link => link.textContent)).toEqual(["#3 Latest change", "Grant access", "#2 Older change", "#1 Other repo", "Grant access"]);
|
|
19
|
+
expect(screen.getByRole("region", { name: "Activity" }).compareDocumentPosition(prs) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
|
20
|
+
expect(screen.getByText("Latest turn worked for 8m")).toBeVisible();
|
|
21
|
+
expect(screen.getByText("Total chat worked for 20m")).toBeVisible();
|
|
22
|
+
expect(within(prs).queryByText("CI passed")).toBeNull();
|
|
23
|
+
expect(screen.getByRole("dialog", { name: task.title })).toBeVisible();
|
|
24
|
+
expect(screen.getAllByText(task.title)).toHaveLength(1);
|
|
25
|
+
expect(screen.getByText("155 subagents")).toBeVisible();
|
|
26
|
+
expect(screen.queryByText("Latest saved reply (excerpt)")).toBeNull();
|
|
27
|
+
const details = screen.getByText("Technical details").closest("details")!;
|
|
28
|
+
expect(details.open).toBe(false);
|
|
29
|
+
fireEvent.click(screen.getByText("Technical details"));
|
|
30
|
+
expect(within(details).getByText("Desktop")).toBeVisible();
|
|
31
|
+
expect(within(details).getByText("Human")).toBeVisible();
|
|
32
|
+
expect(screen.queryByRole("button", {name: "Chat actions"})).toBeNull();
|
|
33
|
+
expect(screen.queryByRole("button", {name: "Copy Chat ID"})).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
test("a missing older PR keeps its warning after that PR rather than the accessible latest PR", () => {
|
|
38
|
+
const task = { ...fixtureTask(), relatedGitHubLinks: [], detailPullRequests: [
|
|
39
|
+
{ url: "https://github.com/example/repo/pull/3", title: "Accessible latest", state: "open" as const, checks: "passed" as const },
|
|
40
|
+
{ url: "https://github.com/example/repo/pull/2", title: "Missing older", state: "unknown" as const, checks: "unknown" as const, accessIssue: "not_found" as const },
|
|
41
|
+
] };
|
|
42
|
+
render(<MantineProvider><NextTaskDetail task={task} opened busy={false} error={null} onClose={() => {}} onOpenCodex={() => {}} /></MantineProvider>);
|
|
43
|
+
expect(within(screen.getByRole("region", { name: "Pull requests" })).getAllByRole("link").map(link => link.textContent)).toEqual(["#3 Accessible latest", "#2 Missing older", "Check access"]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
test("running details use Worked for rather than the finished-turn label", () => {
|
|
48
|
+
const task = { ...fixtureTask(), observed: { archive: false, lastTurnEvent: "inProgress", lastTurnEventAt: new Date(Date.now()-120000).toISOString(), recentFileActivity: true, recordedChatDurationMs: 180000 } };
|
|
49
|
+
render(<MantineProvider><NextTaskDetail task={task} opened busy={false} error={null} onClose={()=>{}} onOpenCodex={()=>{}} /></MantineProvider>);
|
|
50
|
+
expect(screen.getByText("Worked for 2m")).toBeVisible();
|
|
51
|
+
expect(screen.getByText("Total chat worked for 5m")).toBeVisible();
|
|
52
|
+
expect(screen.queryByText(/Latest turn worked for/)).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("usage keeps later-turn tokens visible when historical cost is partial", () => {
|
|
56
|
+
const latest = {tokens: {input_tokens: 100, cached_input_tokens: 20, cache_write_input_tokens: 0, output_tokens: 10, reasoning_output_tokens: 5, total_tokens: 110}, costUsd: 0.2, samples: 1, partial: false, costPartial: false};
|
|
57
|
+
const task = {...fixtureTask(), status: "needs_input" as const, sessionUsage: {latest, total: {...latest,tokens: {...latest.tokens,total_tokens: 10000},costUsd: 3.2, partial: true,costPartial: true}, subagents: 3, pricingDate: "2026-10-10"}};
|
|
58
|
+
render(<MantineProvider><NextTaskDetail task={task} opened busy={false} error={null} onClose={()=>{}} onOpenCodex={()=>{}} /></MantineProvider>);
|
|
59
|
+
const usage=screen.getByRole("region",{name:"Usage"});
|
|
60
|
+
expect(within(usage).getByText("Total chat")).toBeVisible();
|
|
61
|
+
expect(within(usage).getByRole("link", {name: "OpenAI API pricing"})).toHaveAttribute("href", "https://developers.openai.com/api/docs/pricing");
|
|
62
|
+
expect(within(usage).queryByText(/Includes 3 subagents/)).toBeNull();
|
|
63
|
+
expect(within(usage).getByText("110")).toBeVisible();
|
|
64
|
+
expect(within(usage).getByText("≥10.00K")).toBeVisible();
|
|
65
|
+
expect(within(usage).getByRole("columnheader", {name:"Tokens 2"})).toBeVisible();
|
|
66
|
+
expect(within(usage).getByRole("columnheader", {name:"API-equivalent cost 1,2"})).toBeVisible();
|
|
67
|
+
expect(within(usage).getByText("$0.20")).toBeVisible();
|
|
68
|
+
expect(within(usage).getByText("≥$3.20")).toBeVisible();
|
|
69
|
+
expect(within(usage).getByText(/Incomplete estimate:/)).toBeVisible();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("detail header keeps Open chat and Done without the old action menu", () => {
|
|
73
|
+
const open=vi.fn();
|
|
74
|
+
render(<MantineProvider><NextTaskDetail task={fixtureTask()} opened busy={false} error={null} onClose={()=>{}} onOpenCodex={open} extraActions={<button>Mark Done</button>} /></MantineProvider>);
|
|
75
|
+
fireEvent.click(screen.getByRole("button", {name: /^Open chat for/}));
|
|
76
|
+
expect(open).toHaveBeenCalledTimes(1);
|
|
77
|
+
expect(screen.getByRole("button", {name: "Mark Done"})).toBeVisible();
|
|
78
|
+
expect(screen.queryByRole("button", {name: "Chat actions"})).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("unpriced usage marks the cost label with the incomplete footnote", () => {
|
|
82
|
+
const value={tokens:{input_tokens:10,cached_input_tokens:0,cache_write_input_tokens:0,output_tokens:1,reasoning_output_tokens:0,total_tokens:11},samples:1,costUsd:0,costPartial:true,partial:false};
|
|
83
|
+
const task={...fixtureTask(),sessionUsage:{latest:value,total:value,subagents:0,pricingDate:"2026-10-10"}};
|
|
84
|
+
render(<MantineProvider><NextTaskDetail task={task} opened busy={false} error={null} onClose={()=>{}} onOpenCodex={()=>{}} /></MantineProvider>);
|
|
85
|
+
expect(within(screen.getByRole("region",{name:"Usage"})).getAllByText("—")).toHaveLength(2);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
test("Usage stays visible while loading and when data is unavailable", () => {
|
|
90
|
+
const props={task:fixtureTask(),opened:true,busy:false,error:null,onClose:()=>{},onOpenCodex:()=>{}};
|
|
91
|
+
const view=render(<MantineProvider><NextTaskDetail {...props} usageLoading /></MantineProvider>);
|
|
92
|
+
const section=screen.getByRole("region",{name:"Usage"});
|
|
93
|
+
expect(within(section).getByRole("heading",{name:"Usage"})).toBeVisible();
|
|
94
|
+
expect(within(section).getByRole("status")).toHaveTextContent("Loading…");
|
|
95
|
+
view.rerender(<MantineProvider><NextTaskDetail {...props} usageLoading={false} /></MantineProvider>);
|
|
96
|
+
expect(screen.getByRole("region",{name:"Usage"})).toBe(section);
|
|
97
|
+
expect(within(section).getByRole("status")).toHaveTextContent("Usage unavailable.");
|
|
98
|
+
const value={tokens:{input_tokens:100,cached_input_tokens:0,cache_write_input_tokens:0,output_tokens:10,reasoning_output_tokens:0,total_tokens:110},samples:1,costUsd:0.1,costPartial:false,partial:false};
|
|
99
|
+
view.rerender(<MantineProvider><NextTaskDetail {...props} task={{...props.task,sessionUsage:{latest:value,total:value,subagents:0,pricingDate:"2026-10-10"}}} /></MantineProvider>);
|
|
100
|
+
expect(screen.getByRole("region",{name:"Usage"})).toBe(section);
|
|
101
|
+
expect(within(section).getByRole("table")).toBeVisible();
|
|
102
|
+
expect(within(section).queryByRole("status")).toBeNull();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
test("a resolved usage read with zero samples is unavailable", () => {
|
|
107
|
+
const empty={tokens:{input_tokens:0,cached_input_tokens:0,cache_write_input_tokens:0,output_tokens:0,reasoning_output_tokens:0,total_tokens:0},samples:0,costUsd:0,costPartial:true,partial:true};
|
|
108
|
+
render(<MantineProvider><NextTaskDetail task={{...fixtureTask(),sessionUsage:{latest:empty,total:empty,subagents:0,pricingDate:"2026-10-10"}}} opened busy={false} error={null} onClose={()=>{}} onOpenCodex={()=>{}} /></MantineProvider>);
|
|
109
|
+
const usage=screen.getByRole("region",{name:"Usage"});expect(within(usage).getByRole("status")).toHaveTextContent("Usage unavailable.");expect(within(usage).queryByRole("table")).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('Needs answer adds an open-chat action without replacing the saved prompt or response', () => {
|
|
113
|
+
const open=vi.fn();
|
|
114
|
+
const task={...fixtureTask(),needsAnswer:true,pendingQuestions:[{id:'question',title:'May I retry the scan?',options:['Retry once','Stop']}],promptText:'Run the scan.',replyText:'The connection failed. Approval requested.'};
|
|
115
|
+
render(<MantineProvider><NextTaskDetail task={task} opened busy={false} error={null} onClose={()=>{}} onOpenCodex={open}/></MantineProvider>);
|
|
116
|
+
expect(screen.getByText('Needs answer')).toBeInTheDocument();
|
|
117
|
+
const questions=screen.getByRole('region',{name:'Pending questions'});
|
|
118
|
+
expect(within(questions).getByText('May I retry the scan?')).toBeVisible();
|
|
119
|
+
expect(within(questions).getAllByRole('listitem').map(item=>item.textContent)).toEqual(['1Retry once','2Stop']);
|
|
120
|
+
expect(within(questions).getByRole('button',{name:'Open chat to answer'})).toBeVisible();
|
|
121
|
+
expect(screen.getByText('Run the scan.')).toBeInTheDocument();
|
|
122
|
+
expect(screen.getByText('The connection failed. Approval requested.')).toBeInTheDocument();
|
|
123
|
+
fireEvent.click(screen.getByRole('button',{name:'Open chat to answer'}));expect(open).toHaveBeenCalledTimes(1);
|
|
124
|
+
});
|