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
|
@@ -4,95 +4,204 @@ import { act, fireEvent, render, screen, waitFor, within, cleanup } from "@testi
|
|
|
4
4
|
import { useState } from "react";
|
|
5
5
|
import type { Task } from "../../dashboard/react/types";
|
|
6
6
|
|
|
7
|
-
const server = vi.hoisted(() => ({ call: vi.fn() }));
|
|
7
|
+
const server = vi.hoisted(() => ({ call: vi.fn(), openLink: vi.fn(), displayMode: "unknown", hostContextChanged: undefined as undefined | ((context: { displayMode: string }) => void), teardown: undefined as undefined | (() => object) }));
|
|
8
|
+
vi.mock("./ProjectPicker", () => ({ ProjectPicker: ({ data, value, onChange }: { data: Array<{label: string; value: string}>; value: string; onChange: (value: string) => void }) => <select aria-label="Project" value={value} onChange={(event) => onChange(event.target.value)}>{data.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}</select> }));
|
|
8
9
|
vi.mock("@mantine/core", async () => {
|
|
9
10
|
const React = await import("react");
|
|
10
11
|
const wrap = (tag: string) => ({ children, component, ...props }: Record<string, unknown>) => React.createElement(component as string || tag, { "aria-label": props["aria-label"], role: props.role, id: props.id }, children as React.ReactNode);
|
|
11
12
|
return {
|
|
13
|
+
createTheme: (theme: unknown) => theme,
|
|
12
14
|
ActionIcon: ({ children, onClick, ...props }: Record<string, unknown>) => React.createElement("button", { "aria-label": props["aria-label"], onClick }, children as React.ReactNode),
|
|
13
|
-
|
|
15
|
+
Modal: ({ opened, children }: { opened: boolean; children: React.ReactNode }) => opened ? <div role="dialog">{children}</div> : null,
|
|
16
|
+
Alert: wrap("div"), Box: wrap("div"), Button: ({ children, onClick, disabled }: {children: React.ReactNode; onClick?: () => void; disabled?: boolean}) => <button onClick={onClick} disabled={disabled}>{children}</button>, Group: wrap("div"),
|
|
14
17
|
MantineProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
18
|
+
TextInput: ({ value, onChange, ...props }: { value: string; onChange: React.ChangeEventHandler<HTMLInputElement>; "aria-label": string }) => <input aria-label={props["aria-label"]} value={value} onChange={onChange} />,
|
|
15
19
|
Paper: wrap("div"), Stack: wrap("div"), Text: wrap("p"), Title: wrap("h2"),
|
|
16
20
|
SegmentedControl: ({ data, onChange, value, ...props }: { data: Array<{ label: string; value: string }>; onChange: (value: string) => void; value: string; "aria-label": string }) => React.createElement("div", { role: "radiogroup", "aria-label": props["aria-label"] }, data.map((item) => React.createElement("button", { key: item.value, role: "radio", "aria-checked": item.value === value, onClick: () => onChange(item.value) }, item.label))),
|
|
21
|
+
Switch: ({ label, checked, onChange }: { label: string; checked: boolean; onChange: (event: React.ChangeEvent<HTMLInputElement>) => void }) => <label>{label}<input type="checkbox" checked={checked} onChange={onChange} /></label>,
|
|
17
22
|
Select: ({ data, onChange, value, ...props }: { data: Array<{ label: string; value: string }>; onChange: (value: string) => void; value: string; "aria-label": string }) => React.createElement("select", { "aria-label": props["aria-label"], value, onChange: (event: React.ChangeEvent<HTMLSelectElement>) => onChange(event.target.value) }, data.map((item) => React.createElement("option", { key: item.value, value: item.value }, item.label))),
|
|
18
23
|
};
|
|
19
24
|
});
|
|
20
25
|
vi.mock("@modelcontextprotocol/ext-apps", () => ({
|
|
21
26
|
App: class {
|
|
27
|
+
openLink(input: { url: string }) { return server.openLink(input); }
|
|
22
28
|
connect() { return Promise.resolve(); }
|
|
29
|
+
getHostContext() { return { displayMode: server.displayMode }; }
|
|
23
30
|
callServerTool(input: { name: string; arguments: Record<string, unknown> }) { return server.call(input); }
|
|
31
|
+
set onhostcontextchanged(handler: (context: { displayMode: string }) => void) { server.hostContextChanged = handler; }
|
|
32
|
+
set onteardown(handler: () => object) { server.teardown = handler; }
|
|
24
33
|
},
|
|
25
34
|
}));
|
|
26
35
|
vi.mock("../../dashboard/react/components/TaskCard", () => ({
|
|
27
36
|
TaskCard: ({ task, onOpenCodex, onOpenDetail }: { task: Task; onOpenCodex: (task: Task) => void; onOpenDetail: (task: Task) => void }) => <article><button onClick={() => onOpenDetail(task)}>{task.title}</button><button onClick={() => onOpenCodex(task)}>Open chat for {task.title}</button></article>,
|
|
28
37
|
}));
|
|
29
38
|
vi.mock("../../dashboard/react/components/TaskBoard", () => ({
|
|
30
|
-
TaskBoard: ({ tasks, onOpenDetail }: { tasks: Task[]; onOpenDetail: (task: Task) => void }) => <section aria-label="Task board">{tasks.map((task) => <
|
|
39
|
+
TaskBoard: ({ showCardUsage, tasks, onOpenDetail, onOpenCodex, doneNotice, onVisibleTasksChange }: { showCardUsage?: boolean; onVisibleTasksChange?: (ids: string[]) => void; doneNotice?: React.ReactNode; tasks: Task[]; onOpenDetail: (task: Task) => void; onOpenCodex: (task: Task) => void }) => <section aria-label="Task board" data-card-usage={String(showCardUsage)}><button onClick={() => onVisibleTasksChange?.([tasks[0].id])}>Report card in view</button>{doneNotice}{tasks.map((task) => <article key={task.id}><button onClick={() => onOpenDetail(task)}>{task.title}</button><button onClick={() => onOpenCodex(task)}>Open chat for {task.title}</button></article>)}</section>,
|
|
31
40
|
}));
|
|
32
|
-
vi.mock("
|
|
33
|
-
|
|
34
|
-
task: Task | null; opened: boolean; onClose: () => void;
|
|
41
|
+
vi.mock("./NextTaskDetail", () => ({
|
|
42
|
+
NextTaskDetail: ({ task, opened, usageLoading, onClose, onTransition, extraActions }: {
|
|
43
|
+
task: Task | null; opened: boolean; usageLoading?: boolean; onClose: () => void; extraActions?: React.ReactNode;
|
|
35
44
|
onTransition: (status: "completed" | "failed", actionId: string) => Promise<unknown>;
|
|
36
45
|
}) => {
|
|
37
46
|
const [confirm, setConfirm] = useState(false);
|
|
38
|
-
return opened && task ? <section aria-label="Task detail"><h2>{task.title}</h2><p>{task.status}</p><button onClick={onClose}>Close</button><button onClick={() => setConfirm(true)}>Mark completed</button>{confirm && <button onClick={() => { void onTransition("completed", crypto.randomUUID()); setConfirm(false); }}>Confirm</button>}</section> : null;
|
|
47
|
+
return opened && task ? <section aria-label="Task detail"><h2>{task.title}</h2><p>{task.status}</p><section aria-label="Usage">{task.sessionUsage ? "Usage loaded" : usageLoading ? "Loading…" : "Usage unavailable."}</section>{extraActions}<button onClick={onClose}>Close</button><button onClick={() => setConfirm(true)}>Mark completed</button>{confirm && <button onClick={() => { void onTransition("completed", crypto.randomUUID()); setConfirm(false); }}>Confirm</button>}</section> : null;
|
|
39
48
|
},
|
|
40
49
|
}));
|
|
41
50
|
|
|
42
51
|
import { TaskChefApp } from "./main";
|
|
43
52
|
|
|
44
|
-
const task = (id: string, status: Task["status"] = "working", updatedAt =
|
|
53
|
+
const task = (id: string, status: Task["status"] = "working", updatedAt = new Date().toISOString(), project = "Example"): Task => ({
|
|
45
54
|
id, title: `Task ${id}`, instruction: "Do the work", status, updatedAt, createdAt: updatedAt,
|
|
46
55
|
project: { name: project, path: `/${project}`, githubRepos: [] }, summary: "Initial summary",
|
|
47
56
|
threadId: null, turnRef: null, turnId: null, lastResult: null, latestTurn: null,
|
|
48
57
|
});
|
|
49
58
|
|
|
59
|
+
let notificationState = { revision: 0, items: [] as import("./NextNotificationCenter").NextNotification[] };
|
|
60
|
+
let visibility: {showExec: boolean; showCli: boolean; showArchived: boolean; showCardUsage?: boolean} = { showExec: false, showCli: false, showArchived: false };
|
|
50
61
|
let tasks: Task[];
|
|
62
|
+
let registeredProjects: Array<{id: string; name: string; path: string; githubRepos: string[]}>;
|
|
51
63
|
let details: Map<string, Task>;
|
|
52
64
|
let transition: () => Promise<unknown>;
|
|
53
65
|
let detailFailure: string | null;
|
|
54
66
|
beforeEach(() => {
|
|
67
|
+
server.displayMode = "unknown";
|
|
68
|
+
server.hostContextChanged = undefined;
|
|
69
|
+
server.teardown = undefined;
|
|
55
70
|
window.localStorage.clear();
|
|
71
|
+
server.openLink.mockResolvedValue({ isError: false });
|
|
72
|
+
visibility = { showExec: false, showCli: false, showArchived: false };
|
|
73
|
+
notificationState = { revision: 0, items: [] };
|
|
56
74
|
tasks = [task("one")];
|
|
75
|
+
registeredProjects = [];
|
|
57
76
|
details = new Map(tasks.map((item) => [item.id, item]));
|
|
58
77
|
transition = async () => ({ structuredContent: { task: task("one", "completed") } });
|
|
59
78
|
detailFailure = null;
|
|
60
79
|
server.call.mockImplementation(({ name, arguments: args }) => {
|
|
61
|
-
if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { snapshot: { tasks, healthy: true } } });
|
|
80
|
+
if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: { tasks, projects: registeredProjects, healthy: true } } });
|
|
62
81
|
if (name === "taskchef_app_task" && detailFailure) return Promise.resolve({ isError: true, content: [{ type: "text", text: detailFailure }] });
|
|
63
82
|
if (name === "taskchef_app_task") return Promise.resolve({ structuredContent: { task: details.get(args.taskId as string) } });
|
|
83
|
+
if (name === "taskchef_app_notifications") {
|
|
84
|
+
if (args.action === "error") notificationState = { revision: notificationState.revision + 1, items: [{ id: `n${notificationState.revision}`, taskId: args.taskId ?? null, title: args.operation === "settings" ? "Could not open plugin settings" : "Could not open Task one", detail: String(args.error), read: false, timestamp: new Date().toISOString(), kind: "error" }, ...notificationState.items] };
|
|
85
|
+
if (args.action === "clear") notificationState = { revision: notificationState.revision + 1, items: [] };
|
|
86
|
+
if (args.action === "read_all" || args.action === "read") notificationState = { revision: notificationState.revision + 1, items: notificationState.items.map((item) => args.action === "read_all" || item.id === args.id ? { ...item, read: true } : item) };
|
|
87
|
+
return Promise.resolve({ structuredContent: { notifications: notificationState } });
|
|
88
|
+
}
|
|
64
89
|
if (name === "taskchef_app_transition") return transition();
|
|
90
|
+
if (name === "taskchef_app_open_settings") return Promise.resolve({ structuredContent: { message: "Requested settings." } });
|
|
65
91
|
if (name === "taskchef_app_open_chat") return Promise.resolve({ isError: true, content: [{ type: "text", text: "Codex could not be opened." }] });
|
|
66
92
|
throw new Error(`Unexpected tool: ${name}`);
|
|
67
93
|
});
|
|
68
94
|
});
|
|
69
|
-
afterEach(() => { cleanup(); server.call.mockReset(); });
|
|
95
|
+
afterEach(() => { cleanup(); server.call.mockReset(); vi.restoreAllMocks(); });
|
|
70
96
|
function mount() { render(<TaskChefApp />); }
|
|
71
97
|
|
|
72
|
-
test("
|
|
73
|
-
|
|
74
|
-
|
|
98
|
+
test("lifecycle events log readable single strings without suppressing repeats", () => {
|
|
99
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
100
|
+
const { unmount } = render(<TaskChefApp />);
|
|
101
|
+
const details = JSON.stringify({ visibility: document.visibilityState, displayMode: "unknown" });
|
|
102
|
+
expect(warn).toHaveBeenCalledWith(`[TaskChef] TaskChef lifecycle: mounted ${details}`);
|
|
103
|
+
warn.mockClear();
|
|
104
|
+
fireEvent(document, new Event("visibilitychange"));
|
|
105
|
+
expect(warn).toHaveBeenCalledWith(`[TaskChef] TaskChef lifecycle: visibilitychange ${details}`);
|
|
106
|
+
fireEvent(window, new Event("pagehide"));
|
|
107
|
+
fireEvent(window, new Event("pagehide"));
|
|
108
|
+
expect(warn.mock.calls.filter(([message]) => message === `[TaskChef] TaskChef lifecycle: pagehide ${details}`)).toHaveLength(2);
|
|
109
|
+
act(() => server.hostContextChanged?.({ displayMode: "fullscreen" }));
|
|
110
|
+
expect(warn).toHaveBeenCalledWith(`[TaskChef] TaskChef lifecycle: host context changed ${JSON.stringify({ visibility: document.visibilityState, displayMode: "fullscreen" })}`);
|
|
111
|
+
act(() => server.teardown?.());
|
|
112
|
+
expect(warn).toHaveBeenCalledWith(`[TaskChef] TaskChef lifecycle: host teardown ${details}`);
|
|
113
|
+
unmount();
|
|
114
|
+
expect(warn).toHaveBeenCalledWith(`[TaskChef] TaskChef lifecycle: unmounted ${details}`);
|
|
115
|
+
expect(warn.mock.calls.every((call) => call.length === 1 && typeof call[0] === "string" && !call[0].includes("[object Object]"))).toBe(true);
|
|
116
|
+
warn.mockClear();
|
|
117
|
+
fireEvent(window, new Event("pagehide"));
|
|
118
|
+
expect(warn).not.toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("shows only Board, ignores a saved List choice, and filters project and date", async () => {
|
|
122
|
+
window.localStorage.setItem("taskchef.app.view", "list");
|
|
123
|
+
tasks = [task("one", "working", new Date().toISOString(), "Alpha"), task("two", "completed", "2026-01-01T00:00:00Z", "Alpha"), task("three", "interrupted", new Date().toISOString(), "Beta")];
|
|
75
124
|
mount();
|
|
76
|
-
|
|
77
|
-
expect(screen.
|
|
78
|
-
|
|
79
|
-
expect(screen.getByText("Tasks: 2 of 3")).toBeVisible();
|
|
80
|
-
expect(screen.getByRole("radio", { name: "All 2" })).toBeVisible();
|
|
81
|
-
fireEvent.change(screen.getByRole("combobox", { name: "Updated" }), { target: { value: "24h" } });
|
|
82
|
-
expect(screen.getByText("Tasks: 1 of 3")).toBeVisible();
|
|
83
|
-
fireEvent.change(screen.getByRole("combobox", { name: "Updated" }), { target: { value: "all" } });
|
|
84
|
-
fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "" } });
|
|
85
|
-
fireEvent.click(screen.getByRole("radio", { name: /Failed/ }));
|
|
86
|
-
expect(screen.getByText("Tasks: 1 of 3")).toBeVisible();
|
|
87
|
-
expect(screen.queryByRole("button", { name: "Task one" })).not.toBeInTheDocument();
|
|
88
|
-
fireEvent.click(screen.getByRole("radio", { name: "Board" }));
|
|
89
|
-
expect(within(screen.getByRole("region", { name: "Task board" })).getAllByRole("button")).toHaveLength(3);
|
|
125
|
+
await screen.findByRole("button", { name: "Task one" });
|
|
126
|
+
expect(screen.getByRole("region", { name: "Task board" })).toBeVisible();
|
|
127
|
+
expect(screen.queryByRole("radiogroup", { name: "View" })).not.toBeInTheDocument();
|
|
90
128
|
expect(screen.queryByRole("radiogroup", { name: "Status" })).not.toBeInTheDocument();
|
|
91
|
-
fireEvent.
|
|
92
|
-
expect(screen.
|
|
129
|
+
fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "/Alpha" } });
|
|
130
|
+
expect(screen.queryByRole("button", { name: "Task three" })).not.toBeInTheDocument();
|
|
131
|
+
fireEvent.change(screen.getByRole("combobox", { name: "Updated" }), { target: { value: "24h" } });
|
|
93
132
|
expect(screen.queryByRole("button", { name: "Task two" })).not.toBeInTheDocument();
|
|
94
133
|
});
|
|
95
134
|
|
|
135
|
+
test("search finds chat text, projects, PR titles and repositories, combines filters, and keeps the query after refresh", async () => {
|
|
136
|
+
tasks = [task("one", "working", new Date().toISOString(), "Alpha"), task("two", "needs_input", new Date().toISOString(), "Beta"), task("old", "completed", "2026-01-01T00:00:00Z", "Alpha")];
|
|
137
|
+
tasks[0].title = "Fix login";
|
|
138
|
+
tasks[0].replyExcerpt = "First line\nSecond line\n**Database** migration passed";
|
|
139
|
+
tasks[1].replyExcerpt = "Database migration passed";
|
|
140
|
+
tasks[2].replyExcerpt = "Database migration passed";
|
|
141
|
+
tasks[0].project.githubRepos = ["acme/login-service"];
|
|
142
|
+
tasks[1].pullRequests = [{ url: "https://github.com/example/search-engine/pull/12", title: "Improve indexing", state: "open", checks: "unknown" }];
|
|
143
|
+
mount();
|
|
144
|
+
await screen.findByRole("button", { name: "Fix login" });
|
|
145
|
+
const input = screen.getByLabelText("Search Chats");
|
|
146
|
+
const callsBeforeTyping = server.call.mock.calls.length;
|
|
147
|
+
fireEvent.change(input, { target: { value: " LOGIN database " } });
|
|
148
|
+
expect(screen.getByRole("button", { name: "Fix login" })).toBeVisible();
|
|
149
|
+
expect(screen.queryByRole("button", { name: "Task two" })).not.toBeInTheDocument();
|
|
150
|
+
expect(server.call.mock.calls.length).toBe(callsBeforeTyping);
|
|
151
|
+
for (const query of ["Alpha", "login-service"]) {
|
|
152
|
+
fireEvent.change(input, { target: { value: query } });
|
|
153
|
+
expect(screen.getByRole("button", { name: "Fix login" })).toBeVisible();
|
|
154
|
+
expect(screen.queryByRole("button", { name: "Task two" })).not.toBeInTheDocument();
|
|
155
|
+
}
|
|
156
|
+
for (const query of ["Beta", "INDEXING", "search-engine", "example/search-engine", "beta indexing search-engine"]) {
|
|
157
|
+
fireEvent.change(input, { target: { value: query } });
|
|
158
|
+
expect(screen.getByRole("button", { name: "Task two" })).toBeVisible();
|
|
159
|
+
expect(screen.queryByRole("button", { name: "Fix login" })).not.toBeInTheDocument();
|
|
160
|
+
}
|
|
161
|
+
expect(server.call.mock.calls.length).toBe(callsBeforeTyping);
|
|
162
|
+
fireEvent.change(input, { target: { value: "migration" } });
|
|
163
|
+
expect(screen.getByRole("button", { name: "Task two" })).toBeVisible();
|
|
164
|
+
fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "/Alpha" } });
|
|
165
|
+
expect(screen.queryByRole("button", { name: "Task two" })).not.toBeInTheDocument();
|
|
166
|
+
fireEvent.change(screen.getByRole("combobox", { name: "Updated" }), { target: { value: "24h" } });
|
|
167
|
+
expect(screen.queryByRole("button", { name: "Task old" })).not.toBeInTheDocument();
|
|
168
|
+
await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Refresh" })); });
|
|
169
|
+
expect(input).toHaveValue("migration");
|
|
170
|
+
fireEvent.change(input, { target: { value: "not-a-match" } });
|
|
171
|
+
expect(within(screen.getByRole("region", { name: "Task board" })).queryAllByRole("article")).toHaveLength(0);
|
|
172
|
+
fireEvent.change(input, { target: { value: "" } });
|
|
173
|
+
expect(screen.getByRole("button", { name: "Fix login" })).toBeVisible();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("Settings opens the installed plugin details link and reports failure", async () => {
|
|
177
|
+
mount(); await screen.findByRole("button", { name: "Task one" });
|
|
178
|
+
fireEvent.click(screen.getByRole("button", { name: "Settings" }));
|
|
179
|
+
await waitFor(() => expect(server.call).toHaveBeenCalledWith({ name: "taskchef_app_open_settings", arguments: {} }));
|
|
180
|
+
const original = server.call.getMockImplementation()!;
|
|
181
|
+
server.call.mockImplementation((input) => input.name === "taskchef_app_open_settings"
|
|
182
|
+
? Promise.resolve({ isError: true, content: [{ type: "text", text: "Desktop opener failed." }] }) : original(input));
|
|
183
|
+
fireEvent.click(screen.getByRole("button", { name: "Settings" }));
|
|
184
|
+
expect(await screen.findByText("Could not open plugin settings")).toBeVisible();
|
|
185
|
+
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("filters projects with the same name by directory", async () => {
|
|
189
|
+
const first = task("one", null, new Date().toISOString(), "project");
|
|
190
|
+
const second = task("two", null, new Date().toISOString(), "project");
|
|
191
|
+
first.project.path = "/work/first/project";
|
|
192
|
+
second.project.path = "/work/second/project";
|
|
193
|
+
tasks = [first, second];
|
|
194
|
+
mount();
|
|
195
|
+
expect(await screen.findByRole("button", { name: "Task one" })).toBeVisible();
|
|
196
|
+
const picker = screen.getByRole("combobox", { name: "Project" });
|
|
197
|
+
expect(within(picker).getByRole("option", { name: "project (/work/first/project)" })).toBeVisible();
|
|
198
|
+
expect(within(picker).getByRole("option", { name: "project (/work/second/project)" })).toBeVisible();
|
|
199
|
+
fireEvent.change(picker, { target: { value: "/work/second/project" } });
|
|
200
|
+
expect(screen.queryByRole("button", { name: "Task one" })).not.toBeInTheDocument();
|
|
201
|
+
expect(screen.getByRole("button", { name: "Task two" })).toBeVisible();
|
|
202
|
+
expect(within(screen.getByRole("region", { name: "Task board" })).getAllByRole("button", { name: /^Task / })).toHaveLength(1);
|
|
203
|
+
});
|
|
204
|
+
|
|
96
205
|
test("refresh updates detail and removes a disappeared selection", async () => {
|
|
97
206
|
mount();
|
|
98
207
|
fireEvent.click(await screen.findByRole("button", { name: "Task one" }));
|
|
@@ -109,8 +218,8 @@ test("refresh sends the last revision and keeps selection detail current on unch
|
|
|
109
218
|
let revision = 1;
|
|
110
219
|
server.call.mockImplementation(({ name, arguments: args }) => {
|
|
111
220
|
if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: args.revision === revision
|
|
112
|
-
? { unchanged: true, revision }
|
|
113
|
-
: { snapshot: { tasks, healthy: true, revision } } });
|
|
221
|
+
? { unchanged: true, revision, settings: visibility }
|
|
222
|
+
: { settings: visibility, snapshot: { tasks, healthy: true, revision } } });
|
|
114
223
|
if (name === "taskchef_app_task") return Promise.resolve({ structuredContent: { task: details.get(args.taskId as string) } });
|
|
115
224
|
throw new Error(`Unexpected tool: ${name}`);
|
|
116
225
|
});
|
|
@@ -119,7 +228,7 @@ test("refresh sends the last revision and keeps selection detail current on unch
|
|
|
119
228
|
details.set("one", task("one", "needs_input"));
|
|
120
229
|
fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
|
|
121
230
|
await waitFor(() => expect(screen.getByRole("region", { name: "Task detail" })).toHaveTextContent("needs_input"));
|
|
122
|
-
expect(server.call).toHaveBeenCalledWith({ name: "taskchef_app_snapshot", arguments: { revision: 1 } });
|
|
231
|
+
expect(server.call).toHaveBeenCalledWith({ name: "taskchef_app_snapshot", arguments: { revision: 1, force: true, date: "all", project: "", visibleTaskIds: [] } });
|
|
123
232
|
tasks = [task("two")];
|
|
124
233
|
revision = 2;
|
|
125
234
|
fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
|
|
@@ -132,7 +241,7 @@ test("an older selection detail cannot replace a newer refresh detail", async ()
|
|
|
132
241
|
const first = new Promise((resolve) => { releaseFirst = resolve; });
|
|
133
242
|
let detailCalls = 0;
|
|
134
243
|
server.call.mockImplementation(({ name }) => {
|
|
135
|
-
if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { snapshot: { tasks, healthy: true } } });
|
|
244
|
+
if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: { tasks, projects: registeredProjects, healthy: true } } });
|
|
136
245
|
if (name === "taskchef_app_task") return ++detailCalls === 1
|
|
137
246
|
? first
|
|
138
247
|
: Promise.resolve({ structuredContent: { task: task("one", "needs_input") } });
|
|
@@ -147,32 +256,22 @@ test("an older selection detail cannot replace a newer refresh detail", async ()
|
|
|
147
256
|
expect(screen.getByRole("region", { name: "Task detail" })).toHaveTextContent("needs_input");
|
|
148
257
|
});
|
|
149
258
|
|
|
150
|
-
test("shows a card Open chat failure without opening detail", async () => {
|
|
259
|
+
test("shows a card Open chat failure without hiding cards or opening detail", async () => {
|
|
151
260
|
mount();
|
|
152
261
|
fireEvent.click(await screen.findByRole("button", { name: "Open chat for Task one" }));
|
|
153
|
-
expect(await screen.
|
|
262
|
+
expect(await screen.findByText(/Codex could not be opened/)).toBeVisible();
|
|
263
|
+
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
|
264
|
+
expect(screen.getByRole("button", { name: "Task one" })).toBeVisible();
|
|
265
|
+
expect(screen.getByRole("button", { name: "Open chat for Task one" })).toBeVisible();
|
|
154
266
|
expect(screen.queryByRole("region", { name: "Task detail" })).not.toBeInTheDocument();
|
|
155
267
|
});
|
|
156
268
|
|
|
157
|
-
test("transition conflict refreshes the current task", async () => {
|
|
158
|
-
mount();
|
|
159
|
-
fireEvent.click(await screen.findByRole("button", { name: "Task one" }));
|
|
160
|
-
await screen.findByRole("region", { name: "Task detail" });
|
|
161
|
-
transition = async () => {
|
|
162
|
-
details.set("one", task("one", "failed", "2026-10-02T00:00:00Z"));
|
|
163
|
-
throw new Error("Task changed concurrently.");
|
|
164
|
-
};
|
|
165
|
-
fireEvent.click(screen.getByRole("button", { name: "Mark completed" }));
|
|
166
|
-
fireEvent.click(screen.getByRole("button", { name: "Confirm" }));
|
|
167
|
-
await waitFor(() => expect(screen.getByRole("region", { name: "Task detail" })).toHaveTextContent("failed"));
|
|
168
|
-
});
|
|
169
|
-
|
|
170
269
|
test("an older selection response cannot replace the current task", async () => {
|
|
171
270
|
tasks = [task("one"), task("two")];
|
|
172
271
|
let release!: (value: unknown) => void;
|
|
173
272
|
const first = new Promise((resolve) => { release = resolve; });
|
|
174
273
|
server.call.mockImplementation(({ name, arguments: args }) => {
|
|
175
|
-
if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { snapshot: { tasks, healthy: true } } });
|
|
274
|
+
if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: { tasks, projects: registeredProjects, healthy: true } } });
|
|
176
275
|
if (name === "taskchef_app_task") return args.taskId === "one" ? first : Promise.resolve({ structuredContent: { task: task("two") } });
|
|
177
276
|
throw new Error(`Unexpected tool: ${name}`);
|
|
178
277
|
});
|
|
@@ -183,3 +282,354 @@ test("an older selection response cannot replace the current task", async () =>
|
|
|
183
282
|
release({ structuredContent: { task: task("one") } });
|
|
184
283
|
await waitFor(() => expect(screen.getByRole("region", { name: "Task detail" })).toHaveTextContent("Task two"));
|
|
185
284
|
});
|
|
285
|
+
|
|
286
|
+
test("keeps scan diagnostics out of the board", async () => {
|
|
287
|
+
server.call.mockImplementation(({ name }) => {
|
|
288
|
+
if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: {
|
|
289
|
+
tasks: [], healthy: true, revision: 1,
|
|
290
|
+
scan: { source: "database", mode: "database", checkedAt: "2026-10-05T00:00:00Z", intervalSeconds: 5,
|
|
291
|
+
indexedFiles: 8620, visibleFiles: 300, unreadFiles: 8320, errors: 0 },
|
|
292
|
+
} } });
|
|
293
|
+
throw new Error(`Unexpected tool: ${name}`);
|
|
294
|
+
});
|
|
295
|
+
mount();
|
|
296
|
+
await screen.findByRole("region", { name: "Task board" });
|
|
297
|
+
expect(screen.queryByText(/Read-only database/)).not.toBeInTheDocument();
|
|
298
|
+
expect(screen.queryByText(/shown of.*top-level chats/)).not.toBeInTheDocument();
|
|
299
|
+
expect(screen.queryByText(/checks every 5s/)).not.toBeInTheDocument();
|
|
300
|
+
expect(screen.queryByText(/title \(may contain user text\)/)).not.toBeInTheDocument();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("shows an initial inventory failure without undefined scan counts", async () => {
|
|
304
|
+
server.call.mockImplementation(({ name }) => {
|
|
305
|
+
if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: {
|
|
306
|
+
tasks: [], healthy: false, revision: 0,
|
|
307
|
+
scan: { mode: "error", checkedAt: "2026-10-05T00:00:00Z", error: "EACCES" },
|
|
308
|
+
} } });
|
|
309
|
+
throw new Error(`Unexpected tool: ${name}`);
|
|
310
|
+
});
|
|
311
|
+
mount();
|
|
312
|
+
expect(await screen.findByRole("alert")).toHaveTextContent("cannot read the Codex databases");
|
|
313
|
+
expect(screen.queryByText(/Read-only database/)).not.toBeInTheDocument();
|
|
314
|
+
expect(screen.queryByText(/undefined/)).not.toBeInTheDocument();
|
|
315
|
+
expect(screen.queryByText(/shown of/)).not.toBeInTheDocument();
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("inline mode shows a compact recent-chat view", async () => {
|
|
319
|
+
server.displayMode = "inline";
|
|
320
|
+
tasks = [task("one"), task("two"), task("three"), task("four")];
|
|
321
|
+
mount();
|
|
322
|
+
expect(await screen.findByText("4 eligible top-level chats")).toBeVisible();
|
|
323
|
+
expect(screen.getByRole("button", { name: "Task one" })).toBeVisible();
|
|
324
|
+
expect(screen.queryByRole("button", { name: "Task four" })).not.toBeInTheDocument();
|
|
325
|
+
expect(screen.queryByRole("region", { name: "Task board" })).not.toBeInTheDocument();
|
|
326
|
+
expect(screen.queryByText(/View:.*document:/)).not.toBeInTheDocument();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("inline mode loads once and begins polling when expanded", async () => {
|
|
330
|
+
server.displayMode = "inline";
|
|
331
|
+
const setInterval = vi.spyOn(window, "setInterval");
|
|
332
|
+
const clearInterval = vi.spyOn(window, "clearInterval");
|
|
333
|
+
const snapshotCalls = () => server.call.mock.calls.filter(([input]) => input.name === "taskchef_app_snapshot").length;
|
|
334
|
+
mount();
|
|
335
|
+
expect(await screen.findByText("1 eligible top-level chats")).toBeVisible();
|
|
336
|
+
expect(snapshotCalls()).toBe(1);
|
|
337
|
+
expect(setInterval.mock.calls.filter(([, delay]) => delay === 5000)).toHaveLength(0);
|
|
338
|
+
fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
|
|
339
|
+
await waitFor(() => expect(snapshotCalls()).toBe(2));
|
|
340
|
+
server.displayMode = "fullscreen";
|
|
341
|
+
act(() => server.hostContextChanged?.({ displayMode: "fullscreen" }));
|
|
342
|
+
await waitFor(() => expect(snapshotCalls()).toBe(3));
|
|
343
|
+
const pollIndex = setInterval.mock.calls.findIndex(([, delay]) => delay === 5000);
|
|
344
|
+
expect(pollIndex).toBeGreaterThanOrEqual(0);
|
|
345
|
+
await act(async () => { (setInterval.mock.calls[pollIndex][0] as () => void)(); });
|
|
346
|
+
expect(snapshotCalls()).toBe(4);
|
|
347
|
+
server.displayMode = "inline";
|
|
348
|
+
act(() => server.hostContextChanged?.({ displayMode: "inline" }));
|
|
349
|
+
expect(clearInterval).toHaveBeenCalledWith(setInterval.mock.results[pollIndex].value);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("shows the Node requirement when the scanner rejects its runtime", async () => {
|
|
353
|
+
server.call.mockResolvedValue({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: {
|
|
354
|
+
tasks: [], healthy: false, revision: 0,
|
|
355
|
+
scan: { mode: "error", checkedAt: "2026-10-05T00:00:00Z", error: "TaskChef requires Node.js 22.18.0 or later for read-only SQLite (current: 22.17.9)." },
|
|
356
|
+
} } });
|
|
357
|
+
mount();
|
|
358
|
+
expect(await screen.findByRole("alert")).toHaveTextContent("TaskChef requires Node.js 22.18.0 or later");
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("a database failure after a healthy snapshot removes stale tasks", async () => {
|
|
362
|
+
mount();
|
|
363
|
+
expect(await screen.findByRole("button", { name: "Task one" })).toBeVisible();
|
|
364
|
+
server.call.mockImplementation(({ name }) => {
|
|
365
|
+
if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: {
|
|
366
|
+
tasks: [], healthy: false, revision: 2,
|
|
367
|
+
scan: { source: "database", mode: "error", checkedAt: new Date().toISOString() },
|
|
368
|
+
} } });
|
|
369
|
+
throw new Error(`Unexpected tool: ${name}`);
|
|
370
|
+
});
|
|
371
|
+
fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
|
|
372
|
+
expect(await screen.findByRole("alert")).toHaveTextContent("cannot read the Codex databases");
|
|
373
|
+
expect(screen.queryByRole("button", { name: "Task one" })).not.toBeInTheDocument();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
test("Done control calls the local mark tool with the displayed turn and refreshes", async () => {
|
|
378
|
+
const current = { ...task("one", "needs_input"), turnId: "turn-one", observed: { archive: false, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } };
|
|
379
|
+
tasks = [current]; details = new Map([[current.id, current]]);
|
|
380
|
+
server.call.mockImplementation(async ({ name, arguments: args }) => {
|
|
381
|
+
if (name === "taskchef_app_set_done") {
|
|
382
|
+
const next = { ...current, status: args.done ? "completed" : "needs_input", manualDone: args.done } as Task;
|
|
383
|
+
tasks = [next]; details.set(current.id, next);
|
|
384
|
+
return { structuredContent: { task: next } };
|
|
385
|
+
}
|
|
386
|
+
if (name === "taskchef_app_snapshot") return { structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: { tasks, projects: registeredProjects, healthy: true } } };
|
|
387
|
+
if (name === "taskchef_app_task") return { structuredContent: { task: details.get(current.id) } };
|
|
388
|
+
throw new Error(name);
|
|
389
|
+
});
|
|
390
|
+
mount();
|
|
391
|
+
fireEvent.click(await screen.findByRole("button", { name: "Task one" }));
|
|
392
|
+
fireEvent.click(await screen.findByRole("button", { name: "Mark Done" }));
|
|
393
|
+
await waitFor(() => expect(within(screen.getByRole("region", { name: "Task detail" })).getByText("completed")).toBeVisible());
|
|
394
|
+
expect(screen.queryByRole("button", { name: "Reopen" })).not.toBeInTheDocument();
|
|
395
|
+
expect(screen.queryByRole("button", { name: "Mark Done" })).not.toBeInTheDocument();
|
|
396
|
+
expect(server.call).toHaveBeenCalledWith({ name: "taskchef_app_set_done", arguments: { taskId: "one", expectedTurnId: "turn-one", done: true } });
|
|
397
|
+
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
test("native visibility settings update counts and survive view reloads", async () => {
|
|
402
|
+
tasks = [task("desktop"), ...["exec", "cli"].map((source) => ({ ...task(source), observed: { source, archive: false, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } })), { ...task("archive", "archived"), observed: { source: "vscode", archive: true, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } }];
|
|
403
|
+
details = new Map(tasks.map((item) => [item.id, item]));
|
|
404
|
+
const first = render(<TaskChefApp />);
|
|
405
|
+
expect(await screen.findByRole("button", { name: "Task desktop" })).toBeVisible();
|
|
406
|
+
expect(within(screen.getByRole("region", { name: "Task board" })).getAllByRole("button", { name: /^Task / })).toHaveLength(1);
|
|
407
|
+
visibility = { showExec: true, showCli: true, showArchived: true };
|
|
408
|
+
fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
|
|
409
|
+
expect(await screen.findByRole("button", { name: "Task archive" })).toBeVisible();
|
|
410
|
+
expect(within(screen.getByRole("region", { name: "Task board" })).getAllByRole("button", { name: /^Task / })).toHaveLength(4);
|
|
411
|
+
first.unmount(); mount();
|
|
412
|
+
expect(await screen.findByRole("button", { name: "Task exec" })).toBeVisible();
|
|
413
|
+
visibility = { showExec: false, showCli: false, showArchived: false };
|
|
414
|
+
fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
|
|
415
|
+
await waitFor(() => expect(screen.queryByRole("button", { name: "Task exec" })).not.toBeInTheDocument());
|
|
416
|
+
expect(screen.queryByRole("button", { name: "Task cli" })).not.toBeInTheDocument();
|
|
417
|
+
expect(screen.queryByRole("button", { name: "Task archive" })).not.toBeInTheDocument();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("a selected CLI chat closes when its source becomes hidden during refresh", async () => {
|
|
421
|
+
visibility.showCli = true;
|
|
422
|
+
const cli = { ...task("cli"), observed: { source: "cli", archive: false, lastTurnEvent: "inProgress", lastTurnEventAt: null, recentFileActivity: false } };
|
|
423
|
+
tasks = [cli]; details = new Map([[cli.id, cli]]);
|
|
424
|
+
mount();
|
|
425
|
+
fireEvent.click(await screen.findByRole("button", { name: "Task cli" }));
|
|
426
|
+
expect(await screen.findByRole("region", { name: "Task detail" })).toBeVisible();
|
|
427
|
+
// Another visible client, or updated metadata, can make this chat archived.
|
|
428
|
+
const archived = { ...cli, status: "archived" as const, observed: { ...cli.observed, archive: true } };
|
|
429
|
+
tasks = [archived]; details.set(cli.id, archived);
|
|
430
|
+
fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
|
|
431
|
+
await waitFor(() => expect(screen.queryByRole("region", { name: "Task detail" })).not.toBeInTheDocument());
|
|
432
|
+
expect(screen.queryByRole("button", { name: "Task cli" })).not.toBeInTheDocument();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("inline mode follows the exec, CLI and archive visibility settings", async () => {
|
|
436
|
+
server.displayMode = "inline";
|
|
437
|
+
tasks = [task("desktop"), { ...task("exec"), observed: { source: "exec", archive: false, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } }, { ...task("cli"), observed: { source: "cli", archive: false, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } }, { ...task("archive", "archived"), observed: { archive: true, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } }];
|
|
438
|
+
mount();
|
|
439
|
+
expect(await screen.findByText("1 eligible top-level chats")).toBeVisible();
|
|
440
|
+
expect(screen.queryByRole("button", { name: "Task cli" })).not.toBeInTheDocument();
|
|
441
|
+
expect(screen.queryByRole("button", { name: "Task archive" })).not.toBeInTheDocument();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test("notification center restores saved history quietly and shares read/clear actions", async () => {
|
|
445
|
+
notificationState = { revision: 1, items: [{ id: "saved", taskId: "one", title: "Saved notification", detail: "", kind: "ready", read: false, timestamp: new Date().toISOString() }] };
|
|
446
|
+
mount();
|
|
447
|
+
await screen.findByRole("button", { name: "Task one" });
|
|
448
|
+
expect(screen.queryByText("Saved notification")).not.toBeInTheDocument();
|
|
449
|
+
expect(screen.getByRole("button", { name: "Notifications, unread" })).toBeVisible();
|
|
450
|
+
expect(screen.getByRole("button", { name: "Notifications, unread" })).not.toHaveTextContent(/\d/);
|
|
451
|
+
fireEvent.click(screen.getByRole("button", { name: /^Notifications/ }));
|
|
452
|
+
const center = screen.getByRole("region", { name: "Notification center" });
|
|
453
|
+
expect(within(center).getByText("Saved notification")).toBeVisible();
|
|
454
|
+
fireEvent.click(within(center).getByRole("button", { name: "Mark all read" }));
|
|
455
|
+
await waitFor(() => expect(screen.queryByRole("button", { name: "Notifications, unread" })).not.toBeInTheDocument());
|
|
456
|
+
fireEvent.click(within(center).getByRole("button", { name: "Unread" }));
|
|
457
|
+
expect(within(center).getByText("No unread notifications")).toBeVisible();
|
|
458
|
+
fireEvent.click(within(center).getByRole("button", { name: "All" }));
|
|
459
|
+
fireEvent.click(within(center).getByRole("button", { name: /Saved notification/ }));
|
|
460
|
+
expect(await screen.findByRole("region", { name: "Task detail" })).toBeVisible();
|
|
461
|
+
expect(screen.queryByRole("region", { name: "Notification center" })).not.toBeInTheDocument();
|
|
462
|
+
fireEvent.click(screen.getByRole("button", { name: "Close" }));
|
|
463
|
+
fireEvent.click(screen.getByRole("button", { name: /^Notifications/ }));
|
|
464
|
+
fireEvent.click(screen.getByRole("button", { name: "Clear all" }));
|
|
465
|
+
expect(await screen.findByText("No notifications yet")).toBeVisible();
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test("new notifications toast once for five seconds without replacing the board", async () => {
|
|
469
|
+
mount();
|
|
470
|
+
await screen.findByRole("button", { name: "Task one" });
|
|
471
|
+
vi.useFakeTimers();
|
|
472
|
+
try {
|
|
473
|
+
notificationState = { revision: 1, items: [{ id: "new", taskId: "one", title: "Task one is ready for input or review", detail: "", kind: "ready", read: false, timestamp: new Date().toISOString() }] };
|
|
474
|
+
await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Refresh" })); });
|
|
475
|
+
expect(screen.getByText("Task one is ready for input or review")).toBeVisible();
|
|
476
|
+
expect(screen.getByRole("region", { name: "Task board" })).toBeVisible();
|
|
477
|
+
await act(async () => { vi.advanceTimersByTime(4000); });
|
|
478
|
+
expect(screen.getByText("Task one is ready for input or review")).toBeVisible();
|
|
479
|
+
await act(async () => { vi.advanceTimersByTime(1000); });
|
|
480
|
+
expect(screen.queryByText("Task one is ready for input or review")).not.toBeInTheDocument();
|
|
481
|
+
await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Refresh" })); });
|
|
482
|
+
expect(screen.queryByText("Task one is ready for input or review")).not.toBeInTheDocument();
|
|
483
|
+
fireEvent.click(screen.getByRole("button", { name: /^Notifications/ }));
|
|
484
|
+
expect(screen.getByText("Task one is ready for input or review")).toBeVisible();
|
|
485
|
+
fireEvent.keyDown(document, { key: "Escape" });
|
|
486
|
+
expect(screen.queryByRole("region", { name: "Notification center" })).not.toBeInTheDocument();
|
|
487
|
+
expect(screen.getByRole("button", { name: /^Notifications/ })).toHaveFocus();
|
|
488
|
+
} finally { vi.useRealTimers(); }
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
test("a saved notification can open Details for a chat hidden by Settings", async () => {
|
|
493
|
+
const hidden = { ...task("hidden", "archived"), observed: { source: "cli", archive: true } } as Task;
|
|
494
|
+
tasks.push(hidden);
|
|
495
|
+
details.set(hidden.id, hidden);
|
|
496
|
+
notificationState = { revision: 1, items: [{ id: "saved", taskId: "hidden", title: "Task hidden is ready for input or review", detail: "", kind: "ready", read: false, timestamp: new Date().toISOString() }] };
|
|
497
|
+
mount();
|
|
498
|
+
await screen.findByRole("button", { name: "Task one" });
|
|
499
|
+
expect(screen.queryByRole("button", { name: "Task hidden" })).not.toBeInTheDocument();
|
|
500
|
+
fireEvent.click(screen.getByRole("button", { name: /^Notifications/ }));
|
|
501
|
+
fireEvent.click(screen.getByRole("button", { name: /Task hidden is ready/ }));
|
|
502
|
+
expect(await screen.findByRole("region", { name: "Task detail" })).toHaveTextContent("Task hidden");
|
|
503
|
+
fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
|
|
504
|
+
await waitFor(() => expect(screen.getByRole("region", { name: "Task detail" })).toHaveTextContent("Task hidden"));
|
|
505
|
+
expect(screen.queryByRole("button", { name: "Notifications, unread" })).not.toBeInTheDocument();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
test("saved project selection groups original and worktree chats and includes empty projects", async () => {
|
|
510
|
+
tasks = [task("original"), task("worktree"), task("plain")];
|
|
511
|
+
tasks[0].project = { id: "saved", name: "My project", path: "/repo", githubRepos: [] };
|
|
512
|
+
tasks[1].project = { id: "saved", name: "My project", path: "/worktree", githubRepos: [] };
|
|
513
|
+
tasks[2].project = { id: "projectless", name: "No project", path: "/Documents/Codex/date/task", githubRepos: [] };
|
|
514
|
+
registeredProjects = [{ id: "saved", name: "My project", path: "/repo", githubRepos: [] }, { id: "empty", name: "Empty project", path: "/empty", githubRepos: [] }];
|
|
515
|
+
mount(); await screen.findByRole("button", { name: "Task original" });
|
|
516
|
+
fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "saved" } });
|
|
517
|
+
expect(screen.getByRole("button", { name: "Task worktree" })).toBeVisible();
|
|
518
|
+
expect(screen.queryByRole("button", { name: "Task plain" })).not.toBeInTheDocument();
|
|
519
|
+
fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "projectless" } });
|
|
520
|
+
expect(screen.getByRole("button", { name: "Task plain" })).toBeVisible();
|
|
521
|
+
expect(screen.queryByRole("button", { name: "Task original" })).not.toBeInTheDocument();
|
|
522
|
+
fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "empty" } });
|
|
523
|
+
expect(within(screen.getByRole("region", { name: "Task board" })).queryAllByRole("article")).toHaveLength(0);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
test("active scheduled chat hides Mark Done in Details", async () => {
|
|
527
|
+
const current = { ...task("one", "needs_input"), scheduled: true, turnId: "turn-one", observed: { archive: false, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } };
|
|
528
|
+
tasks = [current]; details = new Map([[current.id, current]]);
|
|
529
|
+
mount();
|
|
530
|
+
fireEvent.click(await screen.findByRole("button", { name: "Task one" }));
|
|
531
|
+
expect(await screen.findByRole("region", { name: "Task detail" })).toBeVisible();
|
|
532
|
+
expect(screen.queryByRole("button", { name: "Mark Done" })).not.toBeInTheDocument();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
test("Done connection link opens GitHub controls without leaving the board", async () => {
|
|
537
|
+
mount();
|
|
538
|
+
const link = await screen.findByRole("button", {name:"Connect GitHub"});
|
|
539
|
+
fireEvent.click(link);
|
|
540
|
+
expect(await screen.findByRole("button", {name:"Open plugin settings"})).toBeInTheDocument();
|
|
541
|
+
expect(screen.getByRole("region", {name:"Task board"})).toBeVisible();
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
test("All time loads local records first and passes visible card IDs on the next request", async () => {
|
|
546
|
+
render(<TaskChefApp />);
|
|
547
|
+
await screen.findByRole("button", { name: "Report card in view" });
|
|
548
|
+
expect(server.call).toHaveBeenCalledWith({name:"taskchef_app_snapshot",arguments:{project:"",date:"all",visibleTaskIds:[]}});
|
|
549
|
+
server.call.mockClear();
|
|
550
|
+
fireEvent.click(screen.getByRole("button", { name: "Report card in view" }));
|
|
551
|
+
await waitFor(() => expect(server.call).toHaveBeenCalledWith({name:"taskchef_app_snapshot",arguments:{project:"",date:"all",visibleTaskIds:["one"]}}));
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
test("returning to a visible panel reuses PR cache; manual Refresh forces a check",async()=>{
|
|
556
|
+
vi.spyOn(document,"visibilityState","get").mockReturnValue("visible");
|
|
557
|
+
mount(); await screen.findByRole("button",{name:"Task one"});
|
|
558
|
+
server.call.mockClear();
|
|
559
|
+
fireEvent(document,new Event("visibilitychange"));
|
|
560
|
+
await waitFor(()=>expect(server.call).toHaveBeenCalledWith(expect.objectContaining({name:"taskchef_app_snapshot"})));
|
|
561
|
+
expect(server.call.mock.calls.filter(([input])=>input.name==="taskchef_app_snapshot").every(([input])=>input.arguments.force !== true)).toBe(true);
|
|
562
|
+
server.call.mockClear();
|
|
563
|
+
fireEvent.click(screen.getByRole("button",{name:"Refresh"}));
|
|
564
|
+
await waitFor(()=>expect(server.call).toHaveBeenCalledWith(expect.objectContaining({name:"taskchef_app_snapshot",arguments:expect.objectContaining({force:true})})));
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test("appearance selection changes colors and survives reopening the view", async () => {
|
|
568
|
+
const first = render(<TaskChefApp />);
|
|
569
|
+
fireEvent.click(screen.getByRole("button", { name: "Appearance" }));
|
|
570
|
+
expect(screen.getByRole("radio", { name: "NIGHT" })).toHaveAttribute("aria-checked", "true");
|
|
571
|
+
fireEvent.click(screen.getByRole("radio", { name: "DAY" }));
|
|
572
|
+
expect(document.documentElement.dataset.taskchefTheme).toBe("light");
|
|
573
|
+
expect(window.localStorage.getItem("taskchef.app.theme")).toBe("light");
|
|
574
|
+
fireEvent.click(screen.getByRole("radio", { name: "Catppuccin" }));
|
|
575
|
+
expect(window.localStorage.getItem("taskchef.app.visualTheme.light")).toBe("catppuccin");
|
|
576
|
+
fireEvent.click(screen.getByRole("radio", { name: "NIGHT" }));
|
|
577
|
+
fireEvent.click(screen.getByRole("radio", { name: "Nord" }));
|
|
578
|
+
expect(document.documentElement.dataset.taskchefPalette).toBe("nord");
|
|
579
|
+
fireEvent.click(screen.getByRole("radio", { name: "DAY" }));
|
|
580
|
+
expect(document.documentElement.dataset.taskchefPalette).toBe("catppuccin");
|
|
581
|
+
first.unmount();
|
|
582
|
+
render(<TaskChefApp />);
|
|
583
|
+
fireEvent.click(screen.getByRole("button", { name: "Appearance" }));
|
|
584
|
+
expect(screen.getByRole("radio", { name: "DAY" })).toHaveAttribute("aria-checked", "true");
|
|
585
|
+
expect(screen.getByRole("radio", { name: "Catppuccin" })).toHaveAttribute("aria-checked", "true");
|
|
586
|
+
expect(document.documentElement.dataset.taskchefTheme).toBe("light");
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
test("opening and reopening Details use cached PRs; explicit Refresh requests new status", async () => {
|
|
591
|
+
render(<TaskChefApp />);
|
|
592
|
+
await screen.findByRole("button", {name:"Task one"});
|
|
593
|
+
fireEvent.click(screen.getByRole("button", {name:"Task one"}));
|
|
594
|
+
await screen.findByRole("region", {name:"Task detail"});
|
|
595
|
+
await waitFor(()=>expect(server.call).toHaveBeenCalledWith({name:"taskchef_app_task",arguments:{taskId:"one"}}));
|
|
596
|
+
fireEvent.click(screen.getByRole("button", {name:"Close"}));
|
|
597
|
+
fireEvent.click(screen.getByRole("button", {name:"Task one"}));
|
|
598
|
+
await screen.findByRole("region", {name:"Task detail"});
|
|
599
|
+
expect(server.call.mock.calls.filter(([request])=>request.name==="taskchef_app_task").every(([request])=>!request.arguments.refreshGithub)).toBe(true);
|
|
600
|
+
fireEvent.click(screen.getByRole("button", {name:"Refresh"}));
|
|
601
|
+
await waitFor(()=>expect(server.call).toHaveBeenCalledWith({name:"taskchef_app_task",arguments:{taskId:"one",refreshGithub:true}}));
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
test("a slow Details read finishes across successful and failed board refreshes", async () => {
|
|
605
|
+
const original=server.call.getMockImplementation()!;
|
|
606
|
+
let finish!: (value: unknown) => void, failSnapshot=false;
|
|
607
|
+
server.call.mockImplementation((args)=>{
|
|
608
|
+
if(args.name === "taskchef_app_task") return new Promise(resolve=>{finish=resolve;});
|
|
609
|
+
if(args.name === "taskchef_app_snapshot" && failSnapshot) return Promise.reject(new Error("Snapshot failed"));
|
|
610
|
+
return original(args);
|
|
611
|
+
});
|
|
612
|
+
mount();fireEvent.click(await screen.findByRole("button",{name:"Task one"}));
|
|
613
|
+
expect(await screen.findByText("Loading…")).toBeVisible();
|
|
614
|
+
await act(async()=>{fireEvent(document,new Event("visibilitychange"));});
|
|
615
|
+
expect(server.call.mock.calls.filter(([args])=>args.name === "taskchef_app_task")).toHaveLength(1);
|
|
616
|
+
failSnapshot=true;
|
|
617
|
+
await act(async()=>{fireEvent(document,new Event("visibilitychange"));});
|
|
618
|
+
expect(screen.getByText("Loading…")).toBeVisible();
|
|
619
|
+
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,partial:false,costPartial:false};
|
|
620
|
+
await act(async()=>finish({structuredContent:{task:{...task("one"),sessionUsage:{latest:value,total:value,subagents:0,pricingDate:"2026-10-10"}}}}));
|
|
621
|
+
expect(within(screen.getByRole("region",{name:"Usage"})).getByText("Usage loaded")).toBeVisible();
|
|
622
|
+
expect(screen.queryByText("Loading…")).toBeNull();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
test("native card usage setting defaults on and changes without hiding detail usage", async () => {
|
|
627
|
+
render(<TaskChefApp />);
|
|
628
|
+
const board = await screen.findByRole("region", {name:"Task board"});
|
|
629
|
+
expect(board).toHaveAttribute("data-card-usage", "true");
|
|
630
|
+
visibility = {...visibility, showCardUsage:false};
|
|
631
|
+
fireEvent.click(screen.getByRole("button", {name:"Refresh"}));
|
|
632
|
+
await waitFor(() => expect(board).toHaveAttribute("data-card-usage", "false"));
|
|
633
|
+
fireEvent.click(screen.getByRole("button", {name:"Task one"}));
|
|
634
|
+
expect(await screen.findByRole("region", {name:"Usage"})).toBeInTheDocument();
|
|
635
|
+
});
|