diff --git a/SOURCE_MANIFEST.json b/SOURCE_MANIFEST.json index 8cac7638..7fd01ab2 100644 --- a/SOURCE_MANIFEST.json +++ b/SOURCE_MANIFEST.json @@ -1,6 +1,6 @@ { "schema_version": "thoth-source-preview-manifest-v1", - "created_at": "2026-11-12T05:53:40.032733+00:00", + "created_at": "2026-11-12T07:21:29.752175+00:00", "license": "MIT", "git_history_included": false, "full_suite_verified": false, @@ -478,8 +478,8 @@ }, { "path": "apps/web/src/api/resultEvidence.ts", - "sha256": "f1155987d8b7b4151754a72daef1d541194ae974ec491087f8ab99f93b153e47", - "bytes": 9588 + "sha256": "9a8911d113e4061226b443bd19dcb1911dc41141dbb115f8be7985b7d4e3f249", + "bytes": 9992 }, { "path": "apps/web/src/api/resultEvidenceIdentity.test.ts", @@ -493,8 +493,8 @@ }, { "path": "apps/web/src/api/resultEvidenceRuntime.test.ts", - "sha256": "d7f95bf35c84e6ca15d40a1828aef2ec9b552a487d0f3dc033a756167310abac", - "bytes": 4282 + "sha256": "0ab224cd47a485f159a5fedac254c9991cfe6cc69f9504ac02f8c9c9030b07de", + "bytes": 5818 }, { "path": "apps/web/src/api/resultEvidenceServerContract.test.ts", @@ -568,8 +568,8 @@ }, { "path": "apps/web/src/api/uploadTypes.ts", - "sha256": "55077e62dc103db1732ba91216b61990cd267e03837639fac0042e370509971d", - "bytes": 2481 + "sha256": "d0cb6292986348f493ad07aa3ad7da1cac8b9802c25ee505eff5138d960a11f2", + "bytes": 2480 }, { "path": "apps/web/src/components/AbsenceChecks.test.tsx", @@ -588,8 +588,8 @@ }, { "path": "apps/web/src/components/ActionCompare.tsx", - "sha256": "0955d2f6d0f5ab9b6f3bc7654a0c013658d5dd79542579e349532ff276bae75b", - "bytes": 7925 + "sha256": "1a3100723911d7996b8978cfe588e8002d00c7e1465896e7e300798d4f637dca", + "bytes": 7913 }, { "path": "apps/web/src/components/ActionCompareApproval.test.tsx", @@ -613,23 +613,23 @@ }, { "path": "apps/web/src/components/ActionEffort.tsx", - "sha256": "92c8bc80b892b2c2942df09a524cec8dfd1b96460289717f94e629e5f98e53a7", - "bytes": 7279 + "sha256": "aa141c01fa78ac53c9b7b48917811262b012b5fb2bac40fa23afc4d8b8d6fab0", + "bytes": 7298 }, { "path": "apps/web/src/components/AnswerBody.tsx", - "sha256": "87396d1536d00c5a41fdb6743c0ab3fb81a769c3fae9afcf24d9d7cd7148af76", - "bytes": 1676 + "sha256": "2585378ff73ce37e67619e1ef98578d97d44b582514293386482c5234dd23b17", + "bytes": 1698 }, { "path": "apps/web/src/components/AnswerCitations.test.tsx", - "sha256": "56f1847f0bc524654cbf6c93124f55d60240be9091b4851788aff44c2738cf27", - "bytes": 6095 + "sha256": "e99ef91de9538f951746e7ad686eade831bbcf9c1a46a4fb4aebff9cbd79ea84", + "bytes": 6503 }, { "path": "apps/web/src/components/AnswerHold.tsx", - "sha256": "c2d3d09045ae9da9c822c4ae1a5d9fd9883b7296c5c1e4115f11729c69cf8420", - "bytes": 1253 + "sha256": "f1c97c24eb7d599af5d100dff4f4166e3e7aea66c3ad681a6884e172f4787b5b", + "bytes": 1473 }, { "path": "apps/web/src/components/AnswerReadability.test.tsx", @@ -673,8 +673,8 @@ }, { "path": "apps/web/src/components/Brand.tsx", - "sha256": "c30cfb4d067644d767300d337be89c245aca929deb74eb2aba3729d10f945bbf", - "bytes": 644 + "sha256": "a119e4bfd13767c9c3db3933c465ab0e899d0eb03aef3fc46804116378c391db", + "bytes": 706 }, { "path": "apps/web/src/components/CapabilityWorkbench.test.tsx", @@ -683,8 +683,8 @@ }, { "path": "apps/web/src/components/CapabilityWorkbench.tsx", - "sha256": "23d8fb63acd9a53cb93ec34a0cdd0d47f37cb7d49510d703496d10145fa82ed2", - "bytes": 7752 + "sha256": "3fb5fa29b51d9f96e1e7b5958029ee46dbfe5e88750ff6457988ca066c94cf48", + "bytes": 7655 }, { "path": "apps/web/src/components/CheckedObservations.test.tsx", @@ -733,8 +733,8 @@ }, { "path": "apps/web/src/components/ConfirmedFacts.tsx", - "sha256": "7d8682f120176cfa47ad9f34abae9ba3c5d0d1feba67141ee690edd8565975ed", - "bytes": 3102 + "sha256": "38448e96054a958fff7de6b013fba9c5544edabc889fa478abbd59dd2e46b7e1", + "bytes": 3155 }, { "path": "apps/web/src/components/ConnectFailureInWorkspace.test.tsx", @@ -743,18 +743,23 @@ }, { "path": "apps/web/src/components/ConnectedSourceCard.tsx", - "sha256": "5a3f35e0fab0ed63f7ee66152b353a5cf86ee2040d8921fe5267b4cf83b15b8e", - "bytes": 2147 + "sha256": "2b8a516f96ace07f1c6b567164158cf938b23e0a0c75cbccba66f8b43e200115", + "bytes": 2146 }, { "path": "apps/web/src/components/ContractV3Screens.test.tsx", "sha256": "cc58a42347636f4d117d503d4f7495606cff79c5541327049f6ff543104ffdc0", "bytes": 8555 }, + { + "path": "apps/web/src/components/ConversationEarlierRound.test.tsx", + "sha256": "4aca3030e5519fb03d2fad5b7ab7cedf50f64ce6b6cc2061bc788b687bc7d1e4", + "bytes": 4633 + }, { "path": "apps/web/src/components/ConversationTimeline.tsx", - "sha256": "fac6cb6df248655336792466ee4d4144c07f84345bc8c2fe9b57f6854b7b8b34", - "bytes": 13459 + "sha256": "dc4a30fa2bced151ee36dc7098c48c3dd790d1fee8c5477d727ea625874d0296", + "bytes": 13850 }, { "path": "apps/web/src/components/ConversationTimelineAutoRetry.test.tsx", @@ -798,8 +803,8 @@ }, { "path": "apps/web/src/components/DomainRecords.tsx", - "sha256": "c70020a94bc96d7c47f32b366af3e77c4b80fbd848b96ce88bdedd53bf178022", - "bytes": 3936 + "sha256": "19752aee19b8894a20df5c6ab8a84aa681aec53fd0391f628780cecaceab34ef", + "bytes": 3827 }, { "path": "apps/web/src/components/EffectDeclarationFields.tsx", @@ -813,8 +818,8 @@ }, { "path": "apps/web/src/components/EvidenceRail.tsx", - "sha256": "570f616f7084665f4afede470d23cd3af537bb9f1100e2cd80d9cc7e96e0fc81", - "bytes": 5208 + "sha256": "bfea1fc7080953fd63d72054a5e34e9708c060689a1427b2f1d5d3648adf3c66", + "bytes": 5112 }, { "path": "apps/web/src/components/EvidenceUseSummary.tsx", @@ -843,8 +848,8 @@ }, { "path": "apps/web/src/components/FileConnectionPanel.tsx", - "sha256": "e40358b3d92c64125d9598c51de0db8f67b8cd71be049b64679e964a09785e44", - "bytes": 10937 + "sha256": "f3293ac86d597a6612a008625dfd220b85d736c5dd08a1a67e3c3eff8fea8eb0", + "bytes": 10777 }, { "path": "apps/web/src/components/FileConnectionPanelParserError.test.tsx", @@ -973,8 +978,8 @@ }, { "path": "apps/web/src/components/LiveProjectWorkspace.tsx", - "sha256": "eaba759078b351030d8f3e33938e6781c7d5a3d493dc89d73cc0c1fad5f76170", - "bytes": 28294 + "sha256": "12e5701eb723a065204cef20d37476503d8125be00d5c087dff4b1e458deaa0d", + "bytes": 28033 }, { "path": "apps/web/src/components/LiveProjectWorkspaceConsent.test.tsx", @@ -993,8 +998,8 @@ }, { "path": "apps/web/src/components/LiveProjectWorkspaceRestart.test.tsx", - "sha256": "81b554af04f58f425130e922fce11d93cb6837bd3c7b28ab9592cac4b7b23610", - "bytes": 42955 + "sha256": "c2acb9dfd8885153a4a3392090ce742ed999b5c1a63adca5da024749a6e1abc0", + "bytes": 43945 }, { "path": "apps/web/src/components/LocalRetrievalSettingsCard.test.tsx", @@ -1053,18 +1058,18 @@ }, { "path": "apps/web/src/components/ModelSettings.test.tsx", - "sha256": "2482b76f2217307f51d132bd7682ed726ed4a40bb626d03ad1c0e7b53a73ec91", - "bytes": 28696 + "sha256": "0b7dcfe90074e28a61dae40fe6a38c1a88ab8a6fbebd2fe08ae87870f560507e", + "bytes": 28719 }, { "path": "apps/web/src/components/ModelSettings.tsx", - "sha256": "8f65487fb10690176b9bd5b2cdccf18615f9a8086625c532ed24a97fe050062c", - "bytes": 10717 + "sha256": "0757d29a8dcddc0302d93749b7b3395766e44fde1131ae14a022dc5a857cf104", + "bytes": 10724 }, { "path": "apps/web/src/components/NavigationDrawer.test.tsx", - "sha256": "7fb0866c71df0cad35946a0f6e7cf81eef101893f665ae1fd93de9750aaeee05", - "bytes": 11618 + "sha256": "06a669a705a06426260af646f6160fab438411c3264142d2d66c4829e5499f26", + "bytes": 11617 }, { "path": "apps/web/src/components/OAuthLoginFlow.tsx", @@ -1083,13 +1088,13 @@ }, { "path": "apps/web/src/components/ProjectForm.test.tsx", - "sha256": "070d13dc35cbb44922f48f503a7b760813f8f1789c56c96494691ee2c53c0e3c", - "bytes": 1608 + "sha256": "6bcf342d5e95785ffa4970491fdd39f219b43bc131ac266896406a247c04d154", + "bytes": 1607 }, { "path": "apps/web/src/components/ProjectForm.tsx", - "sha256": "5d1764bbd5a0e00ca79e54e2e567e4bdd0024fd44d00049884718019e80267de", - "bytes": 3839 + "sha256": "9e767d7ebe9428945219d1a34d9772a44c6584ad921fa00a4b2a20497dcef33f", + "bytes": 3838 }, { "path": "apps/web/src/components/ProjectLanding.test.tsx", @@ -1133,8 +1138,8 @@ }, { "path": "apps/web/src/components/ProjectSessionPages.tsx", - "sha256": "10297ae274e340a734474fe4ac873d6d49a0c9bcb95bb8b9b2a06bc54039a845", - "bytes": 10850 + "sha256": "3ac79479989184e9a69b18ef3e99f41adde096a01c9c7301a52e13bf8808d083", + "bytes": 10736 }, { "path": "apps/web/src/components/ProjectWebSetup.test.tsx", @@ -1238,8 +1243,8 @@ }, { "path": "apps/web/src/components/ResearchResultCard.tsx", - "sha256": "46a2f696960986d294c97e55ec6250fd91a91bc47ca756d372e8cef706c22ddd", - "bytes": 9398 + "sha256": "2b0c9f3492f68b79f12cc64ed370d439dc925577175da94b1aaf0e0574223813", + "bytes": 9512 }, { "path": "apps/web/src/components/ResearchSidePanel.tsx", @@ -1248,13 +1253,13 @@ }, { "path": "apps/web/src/components/ResourcesPage.test.tsx", - "sha256": "2e827a36757498a1df4bf07bddbcffc383cdf9829531b695338f53a1457aa57d", - "bytes": 6748 + "sha256": "e867dba8d2646895313bdfebe65244abf38ce6a469a0baad355a6d9f93c3328e", + "bytes": 6747 }, { "path": "apps/web/src/components/ResultCardOnce.test.tsx", - "sha256": "783a5bbb3819301c5aea8e5408ff723e479f3d3dea947a240ae879317e58038c", - "bytes": 13346 + "sha256": "0b9ddeb1633417558b061bbd50eaf2becc0f54daa2ccf6487da1c91b83fbeb54", + "bytes": 13886 }, { "path": "apps/web/src/components/ResultEvidencePanel.tsx", @@ -1268,8 +1273,8 @@ }, { "path": "apps/web/src/components/ResultHoldDetails.tsx", - "sha256": "df563dab05fdcd5a01a93cb4b03fab892d7980506eb5734d373f93a42301b8ca", - "bytes": 3577 + "sha256": "6b378960876ac027cf61328b3fd9fdf4b35a57b6927f0e04ec71b081b13969e4", + "bytes": 3742 }, { "path": "apps/web/src/components/ResultInterruptionNotice.test.tsx", @@ -1316,6 +1321,16 @@ "sha256": "7903291351348081e58962e3589f63a929ee70e3f3b53d2249486a0bc897b056", "bytes": 4538 }, + { + "path": "apps/web/src/components/Select.test.tsx", + "sha256": "5541d260758ef0a8cee89ddb461848ed89c0ae33118fcd8f832c950165e9c900", + "bytes": 1353 + }, + { + "path": "apps/web/src/components/Select.tsx", + "sha256": "4da7d2e6a180c885d14d51e7aab538def5eaa6c8691063a318ed9fd25edeeb30", + "bytes": 625 + }, { "path": "apps/web/src/components/Sentences.test.tsx", "sha256": "ea1a23a11154e20d43e42bb7fb98778998c4708637feb2a6d07e8391446d56a2", @@ -1343,8 +1358,8 @@ }, { "path": "apps/web/src/components/SourceTimeAdvanced.tsx", - "sha256": "f686c4d7cb31caee10fa99ffefb393e436037530d4082465f4b016db339d9435", - "bytes": 3742 + "sha256": "0683561a5721cd80fee8e3269bef1eef49602679caffc31d89319cb413126db5", + "bytes": 3761 }, { "path": "apps/web/src/components/SourceTimeConfirmationDialog.tsx", @@ -1493,8 +1508,8 @@ }, { "path": "apps/web/src/components/WorkspaceTopbar.tsx", - "sha256": "ac9075e74c3802b1153498c6012aaeff44268772d03d8435d39dc00e7470e6c8", - "bytes": 3697 + "sha256": "5ad5cf984d158091501c56d51fee1096e4eda7e8cb0ff64d6082056a1d25f35b", + "bytes": 3696 }, { "path": "apps/web/src/components/XaiDeviceLogin.tsx", @@ -1658,8 +1673,8 @@ }, { "path": "apps/web/src/components/history/ResearchHistoryWorkspace.tsx", - "sha256": "2d4c02e54a6881ca7566fe422a673dfd585080f300887a3b7e9fd7ee630b6bc0", - "bytes": 11236 + "sha256": "1c0b7f73e066c4428df11d6296c7efef9e21efe3e98abc580bb19f0e779daf25", + "bytes": 11127 }, { "path": "apps/web/src/components/history/RestorePreviewDialog.tsx", @@ -1803,8 +1818,8 @@ }, { "path": "apps/web/src/components/statusLabels.test.tsx", - "sha256": "87fb0355ac6330c1736a59f6f94b0a8a05630e9ccbbd48beded5e09f120bfc64", - "bytes": 2642 + "sha256": "ca6ff4d5c735e2da417140c0621df722669681f36559b92cc077cd73ccc3715d", + "bytes": 2656 }, { "path": "apps/web/src/components/statusLabels.ts", @@ -1928,8 +1943,8 @@ }, { "path": "apps/web/src/components/useNavigationDrawer.ts", - "sha256": "29fddd758ba9646a2e471fb41e92eb28ec2469c8722a32d92221f5db07c3afae", - "bytes": 2324 + "sha256": "6c4fbaf3ae858673ed4844a738d28373c686ea7e941ccf7478f611c3925213e7", + "bytes": 2323 }, { "path": "apps/web/src/components/useOpenAtLatestTurn.test.tsx", @@ -2013,8 +2028,8 @@ }, { "path": "apps/web/src/i18n/apiNoticesMessages.ts", - "sha256": "3ba1dd571e9d90553c52e6960011eadfac557274964a60d078d0727ec024654f", - "bytes": 5433 + "sha256": "91437790ffadd57ec2dbd2fa27ed8ec4829d2ecac683e747295fa33945142e10", + "bytes": 5703 }, { "path": "apps/web/src/i18n/bilingualRecord.ts", @@ -2038,8 +2053,8 @@ }, { "path": "apps/web/src/i18n/countFormatMessages.ts", - "sha256": "27b8a6cdc4bd769f2fec9be20dfd8488996dd548706c77d4839af5325edd4cb7", - "bytes": 15890 + "sha256": "0e98dad151c5957478089a6385a09187341ee350303485c503df2433c607d3a4", + "bytes": 15873 }, { "path": "apps/web/src/i18n/credentialMessages.ts", @@ -2058,7 +2073,7 @@ }, { "path": "apps/web/src/i18n/formatMessages.ts", - "sha256": "ccbb8177183c287db993e52fb3cd0b4599dabc41f1eb6773159f3481e0b06e04", + "sha256": "40d9cc45b3273cb3f31151e622a745327650548df4758f7119ec9a993995eedc", "bytes": 9435 }, { @@ -2108,8 +2123,8 @@ }, { "path": "apps/web/src/i18n/ja/apiNoticesMessagesJa.ts", - "sha256": "fcefdb580895c5956b4b62c2f80d8e284ba594e64cecbfa44232d8ba54bb5d21", - "bytes": 3282 + "sha256": "e24784808bd10e8055d1e3db7100de1b8568bb2315b7c6a663ce6fd4158add25", + "bytes": 3444 }, { "path": "apps/web/src/i18n/ja/commonStateMessagesJa.ts", @@ -2213,8 +2228,8 @@ }, { "path": "apps/web/src/i18n/ja/sourceMessagesJa.ts", - "sha256": "46e1ab3bf54406a41a884d016eba666de66731ceed7824e70873a1da6e0a6c52", - "bytes": 7561 + "sha256": "cce728a983fc6743a63d3f5bcaa944901261ef5748218854b7e454c8d11fa007", + "bytes": 7478 }, { "path": "apps/web/src/i18n/ja/traceMessagesJa.ts", @@ -2223,13 +2238,13 @@ }, { "path": "apps/web/src/i18n/ja/workspaceDetailMessagesJa.ts", - "sha256": "a8eb7c0034c0d5c39bf0963235990991b6ea4805aa3a7483f38153bd43f0e0f3", - "bytes": 2855 + "sha256": "d68a6d078258343eb6a5326b21a4f3097d3d707195a22b2e2c3243fa4f3b67a4", + "bytes": 2754 }, { "path": "apps/web/src/i18n/ja/workspaceMessagesJa.ts", - "sha256": "8d94ed5f4d8c90514b0bf4bdc11bc230e2e6844f2b94516c2d21bf6626ea75f5", - "bytes": 9823 + "sha256": "213e1cf46aedd471a9ce95b50d78fef67aa2aa7587cb3aa0725a731cbed8e2f4", + "bytes": 9621 }, { "path": "apps/web/src/i18n/jaScreen.test.ts", @@ -2363,8 +2378,8 @@ }, { "path": "apps/web/src/i18n/sourceMessages.ts", - "sha256": "5030d7a6e025496f03c72290402394b5bbe7698d8a58dc00888d1946d12043c1", - "bytes": 11986 + "sha256": "8e69990033fe21b6cab89fd29b058f62a00972c4738b21ca7e476044b24587a0", + "bytes": 11850 }, { "path": "apps/web/src/i18n/stateFormatMessages.ts", @@ -2373,28 +2388,28 @@ }, { "path": "apps/web/src/i18n/statusChinese.ts", - "sha256": "7feb330c61f6662b4e891f17cf198ac2ec3b60eb8ed86b1975f121b804e87325", - "bytes": 5275 + "sha256": "beb99204454e2a8341fb4843949c1b6e8ff77f4a0760da2c34ffc8f7d0fe1d3d", + "bytes": 5043 }, { "path": "apps/web/src/i18n/statusEnglish.ts", - "sha256": "6dac000931f26ad7edb83f9d8f544ca5f852f7aa668ca43fdea5e7614d007b8a", - "bytes": 5526 + "sha256": "caf375733d39ad9e9cd9da57fb770b0320c12fec2cb373c65f780099f7edb041", + "bytes": 5282 }, { "path": "apps/web/src/i18n/statusJapanese.ts", - "sha256": "ff09b8f2d71049724a91efd8e0f649f5e743c4f61480cd76391fb871e3d6a4c2", - "bytes": 6074 + "sha256": "e52df8d27d736da76c0e51d8209ba3a11d3f088c586fd9eb0972d4c02e146c49", + "bytes": 5812 }, { "path": "apps/web/src/i18n/statusResources.ts", - "sha256": "7cfa0b6fd0aac8e97cc2ca12047c468dd922d05c660bac6cf0573ae2aee6b38e", - "bytes": 6823 + "sha256": "6ccc34dff1bfd6c10dbbbcfdec2997705e058c3f6eef9e9d182134a0130cff56", + "bytes": 6592 }, { "path": "apps/web/src/i18n/statusTraditional.ts", - "sha256": "033c1acec5e608d605246c02bb77dc90d1d4211b86f91b30f433904c2d7924c6", - "bytes": 5279 + "sha256": "7233017698241aa41524877c9386adb84e2531107264b3c85ad8b24d84d3513c", + "bytes": 5047 }, { "path": "apps/web/src/i18n/testOrderResources.ts", @@ -2448,18 +2463,18 @@ }, { "path": "apps/web/src/i18n/wordingRules.test.ts", - "sha256": "8e3102e3de9c6e24ff70fc8f37205db1612d0f77333783c7f973968b3c74fc8b", - "bytes": 10468 + "sha256": "c1123ae5a3befebc886159ec32dc0037012a106fd190dee2c1ca9a7d6cc6e3e2", + "bytes": 13078 }, { "path": "apps/web/src/i18n/workspaceDetailMessages.ts", - "sha256": "5bc7df63f4c8b1b8dc9b497c14c632dc5970c31af716cd34f8bb24ad799b0f86", - "bytes": 4198 + "sha256": "5b4e5950f9c30b32f331ea09673efaf4dfcea273b9b9b17e5d28d57e5c09c5e4", + "bytes": 4031 }, { "path": "apps/web/src/i18n/workspaceMessages.ts", - "sha256": "af89ca11c886b071b88ef155bdc1bcc32ddcff31aa7e45fa4cd7ea97848b4a4b", - "bytes": 16055 + "sha256": "74cf1cf623915a5e6ce1bc39d6ed8c89540795f084683099198d1ba8a713f45d", + "bytes": 15708 }, { "path": "apps/web/src/i18n/zh/activityMessagesZh.ts", @@ -2473,8 +2488,8 @@ }, { "path": "apps/web/src/i18n/zh/apiNoticesMessagesZh.ts", - "sha256": "eaff16743e63e01a29d55845472c0825dc6d6649e7ec06050936c0263a1bfaf4", - "bytes": 2537 + "sha256": "0708bf415de42d38033f94c796b1b3e250911e67843e9b37aabfb5e7d98f2020", + "bytes": 2663 }, { "path": "apps/web/src/i18n/zh/commonStateMessagesZh.ts", @@ -2578,8 +2593,8 @@ }, { "path": "apps/web/src/i18n/zh/sourceMessagesZh.ts", - "sha256": "543014a38dd33e23887db4a632a74553d09791e8f8f80fcda11553688e28cf17", - "bytes": 5379 + "sha256": "b60b448c8dcebcc60fb8ed8923ca91e8ff99bb6a32bcb39778df181f2e91c5ba", + "bytes": 5323 }, { "path": "apps/web/src/i18n/zh/traceMessagesZh.ts", @@ -2588,13 +2603,13 @@ }, { "path": "apps/web/src/i18n/zh/workspaceDetailMessagesZh.ts", - "sha256": "8602dd90df6301c6b4c528ec4716dfca51c5c524ea05822febad381599133c58", - "bytes": 2090 + "sha256": "75c523a6d22f8c4a9c59cd8b51103df430a85d2d151136ac82fbbf164884ee99", + "bytes": 2007 }, { "path": "apps/web/src/i18n/zh/workspaceMessagesZh.ts", - "sha256": "0baa8b48bfd7a08c043f16f8f7f959696c3d27bfb50bf54dafef9a396736e310", - "bytes": 7631 + "sha256": "6562f00fb08f60eab15cd5595dc324887af7578c9988c34d61f3ab75ac621c20", + "bytes": 7480 }, { "path": "apps/web/src/i18n/zhHant/activityMessagesZhHant.ts", @@ -2608,8 +2623,8 @@ }, { "path": "apps/web/src/i18n/zhHant/apiNoticesMessagesZhHant.ts", - "sha256": "1e175c1ede81021bb4bb5a151393f0041f6b698c11569affef409031402eeef2", - "bytes": 2541 + "sha256": "56aa8eacb6c8c88fe09895e71bee4eb5679fe6da1e686ada31395670717c53e3", + "bytes": 2667 }, { "path": "apps/web/src/i18n/zhHant/commonStateMessagesZhHant.ts", @@ -2713,8 +2728,8 @@ }, { "path": "apps/web/src/i18n/zhHant/sourceMessagesZhHant.ts", - "sha256": "795a604a027b1d0b11410dbbc358d0bee62ae988950925fafba9f92b51312eb4", - "bytes": 5392 + "sha256": "308f999549c2cb9eda5ee0f550e98612ee2f76403f061c7386fa54a4b8eab6e1", + "bytes": 5336 }, { "path": "apps/web/src/i18n/zhHant/traceMessagesZhHant.ts", @@ -2723,13 +2738,13 @@ }, { "path": "apps/web/src/i18n/zhHant/workspaceDetailMessagesZhHant.ts", - "sha256": "ed053f2fe179e13cb11a3b84bf56e158136354c7708a545adb14b7c4c96dbe3d", - "bytes": 2088 + "sha256": "29dd70f4e7ecbf071604177f8d2a5c861dc88c6aebdad314769b4bc35833d92f", + "bytes": 2005 }, { "path": "apps/web/src/i18n/zhHant/workspaceMessagesZhHant.ts", - "sha256": "575282047e728fc53214b69a79606e964a6c017324ed93de1455f8b2bd39baf1", - "bytes": 7665 + "sha256": "e08758b72bb82891b0f88805e02db8053a123401ba235a99ef2939fd97de8793", + "bytes": 7514 }, { "path": "apps/web/src/i18n/zhHantScreen.test.ts", @@ -2748,8 +2763,8 @@ }, { "path": "apps/web/src/narrowLayoutRules.test.ts", - "sha256": "ed05cbc9cf5895079bb33b327fcb731e16eb3d515a0b7327591d7d8d5beb4559", - "bytes": 5845 + "sha256": "e1ce29dc550b80197befd2067339f6d36d08d341218a9337e3ab722d38bc1761", + "bytes": 6689 }, { "path": "apps/web/src/preview-timeline.tsx", @@ -2788,8 +2803,8 @@ }, { "path": "apps/web/src/typeScaleRules.test.ts", - "sha256": "5c68854bcb2a2347d677a20904ca87d1cee23eb295b70e5b74e19f7b85825737", - "bytes": 1921 + "sha256": "144e34be972db8f63f42bea91cafd73cb81d41b9abb9e45f6ac476ad8365212a", + "bytes": 1920 }, { "path": "apps/web/src/types.ts", @@ -2803,8 +2818,8 @@ }, { "path": "apps/web/src/workstation.css", - "sha256": "7e9842f7f1d5020f79ba299d8bf42d96adba4cabff72927d865570a66fb6c6d7", - "bytes": 74239 + "sha256": "ed5fe3509cb88b96ee93264fb467a67a0192c0002ed8802b129a1dae74fc498d", + "bytes": 74820 }, { "path": "apps/web/timeline-preview.html", diff --git a/apps/web/src/api/resultEvidence.ts b/apps/web/src/api/resultEvidence.ts index d4c0f253..71644eb0 100644 --- a/apps/web/src/api/resultEvidence.ts +++ b/apps/web/src/api/resultEvidence.ts @@ -66,9 +66,13 @@ function bindAnswer(selection: ResultSelection, displayed: Record Object.hasOwn(resultBody, key))) mismatch(); + // The latest round is shown from the summary view. A saved earlier round is shown from the stored result itself + // (operation/result/read returns it whole). What the screen shows must be, all of it, a part of one of these forms + // of the same stored answer; keys taken from two forms that disagree are refused. + const shownIsStored = [shownBody, resultBody, manifestBody].some(body => isShownPartOf(displayedContent, body)); if (manifest.operation_id !== selection.operationId || manifest.request_ref.project_id !== scope.projectId || manifest.request_ref.entity_type !== "THREAD" || manifest.request_ref.entity_id !== `request:${thread}` || manifest.request_ref.revision_digest !== scope.requestDigest || - resultContentKey(without(resultBody, projected)) !== resultContentKey(without(manifestBody, projected)) || !isShownPartOf(displayedContent, shownBody)) mismatch(); + resultContentKey(without(resultBody, projected)) !== resultContentKey(without(manifestBody, projected)) || !shownIsStored) mismatch(); } async function read(selection: ResultSelection, displayed: Record, digest: string | undefined, offset: number, expand: boolean, signal?: AbortSignal) { diff --git a/apps/web/src/api/resultEvidenceRuntime.test.ts b/apps/web/src/api/resultEvidenceRuntime.test.ts index 42d03cf5..efd43341 100644 --- a/apps/web/src/api/resultEvidenceRuntime.test.ts +++ b/apps/web/src/api/resultEvidenceRuntime.test.ts @@ -46,6 +46,26 @@ describe.each(["stored_current", "stored_before_grounded_rows"] as const)("the a serve(c, value => { value.result = { ...value.result, answer: "another answer" }; value.shown_result = { ...value.shown_result, answer: "another answer" }; }); await expect(show(c, { ...c.displayed, answer: "another answer" })).rejects.toThrow(BINDING); }); + // An earlier round of a conversation is shown from operation/result/read, which returns the stored result whole, + // not the summary view the latest round is shown from. + it("opens the evidence for an earlier round, which the screen shows from the stored result itself", async () => { + serve(c); + const whole = c.read.result as Wire; + // it is not the summary view: at least one of the two summarized records is there in full + expect(["absence_assessments", "checked_observations"].some(key => JSON.stringify(whole[key]) !== JSON.stringify(c.displayed[key]))).toBe(true); + expect((await show(c, whole)).selected_count).toBeGreaterThan(0); + expect((await show(c, c.read.manifest.result as Wire)).selected_count).toBeGreaterThan(0); + }); + it("still rejects a whole result with another answer or an invented key, and one put together from two forms", async () => { + serve(c); + const whole = c.read.result as Wire; + await expect(show(c, { ...whole, answer: "another answer" })).rejects.toThrow(BINDING); + await expect(show(c, { ...whole, invented_key: 1 })).rejects.toThrow(BINDING); + // the summary of one record inside the whole result: each key is right in one form, the whole is right in none + const mixedKey = (["absence_assessments", "checked_observations"] as const).find(key => JSON.stringify(whole[key]) !== JSON.stringify(c.displayed[key])); + expect(mixedKey).toBeDefined(); + await expect(show(c, { ...whole, [mixedKey!]: c.displayed[mixedKey!] })).rejects.toThrow(BINDING); + }); }); describe("a server that does not send shown_result or projected_keys (older)", () => { diff --git a/apps/web/src/api/uploadTypes.ts b/apps/web/src/api/uploadTypes.ts index 4bdfbc37..90ce30db 100644 --- a/apps/web/src/api/uploadTypes.ts +++ b/apps/web/src/api/uploadTypes.ts @@ -44,4 +44,3 @@ export function refusalBeforeUpload(file: Pick): UploadRe if (file.size > MAX_UPLOAD_BYTES) return new UploadRefused("UPLOAD_TOO_LARGE", file.name); return null; } - diff --git a/apps/web/src/components/ActionCompare.tsx b/apps/web/src/components/ActionCompare.tsx index 71059e67..dccbd66c 100644 --- a/apps/web/src/components/ActionCompare.tsx +++ b/apps/web/src/components/ActionCompare.tsx @@ -50,7 +50,7 @@ export function ActionCompare({ result, projectId, links, discrimination }: { const evaluations = objectList(analysis.evaluations); const extra = [...actions.flatMap(action => stringValues(action.source_refs)), ...evaluations.flatMap(item => stringValues(item.evidence_refs))]; const numbers = numberSources(typeof result.answer === "string" ? result.answer : "", extra); - const cite = (refs: string[]) => refs.map(ref => [{numbers.get(ref)}]); + const cite = (refs: string[]) => refs.map(ref => [{numbers.get(ref)}]); if (actions.length === 0) return

{t("actions.empty")}

; const againstNote = (action: Record) => standingActionNote([...stringValues(action.hypothesis_ids), ...stringValues(action.hypothesis_refs)], discrimination); diff --git a/apps/web/src/components/ActionEffort.tsx b/apps/web/src/components/ActionEffort.tsx index 6cab4160..68d03155 100644 --- a/apps/web/src/components/ActionEffort.tsx +++ b/apps/web/src/components/ActionEffort.tsx @@ -3,7 +3,8 @@ import { useDraftGeneration, type DraftTicket } from "./useDraftGeneration"; import { useReadAccessProtection } from "./useReadAccessProtection"; import { message as formatMessage } from "../i18n/formatMessages"; import { t, useTranslation } from "../i18n/locale"; -import { Button, Callout, HTMLSelect, InputGroup } from "@blueprintjs/core"; +import { Button, Callout, InputGroup } from "@blueprintjs/core"; +import { Select } from "./Select"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { objectValue, textValue } from "../api/presentation"; @@ -83,7 +84,7 @@ export function ActionEffort({ projectId, actionId, initial, legacy }: { project {open &&
{DIMENSIONS.map(dimension =>
)}
diff --git a/apps/web/src/components/FileConnectionPanel.tsx b/apps/web/src/components/FileConnectionPanel.tsx index 7709348b..c124bffd 100644 --- a/apps/web/src/components/FileConnectionPanel.tsx +++ b/apps/web/src/components/FileConnectionPanel.tsx @@ -1,13 +1,13 @@ import { message } from "../i18n/formatMessages"; import { t, useTranslation } from "../i18n/locale"; -import { Button, Callout, FileInput, HTMLSelect } from "@blueprintjs/core"; +import { Button, Callout, FileInput } from "@blueprintjs/core"; +import { Select } from "./Select"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useEffect, useRef, useState } from "react"; import { stageFile, type StagedFile, type UploadProgress } from "../api/files"; import { UiError } from "../i18n/UiError"; import { sentenceGap } from "../i18n/sentences"; import { rpc } from "../api/rpcClient"; -import { eyebrowLabel } from "./statusLabels"; import { authorityOptions, sourceConnectSchema } from "../api/sourcePolicy"; import { parserFailureText } from "../api/parserError"; import { refusalBeforeUpload, UPLOAD_ACCEPT, UploadRefused } from "../api/uploadTypes"; @@ -99,10 +99,10 @@ export function FileConnectionPanel({ projectId, hosted = false }: { projectId: } }); const progress = progressText(uploadProgress, connectingName); return
-

{eyebrowLabel("AUTHORIZED SOURCES")}

{t("sources.fileTitle")}

{t("sources.preserve")}
+

{t("sources.fileTitle")}

{t("sources.fileTypes")}{hosted?t("sources.hostedUpload"):t("sources.localUpload")}

{ selectionVersion.current += 1; setNotice(null); setUploadProgress(null); setConnectingName(""); setFiles(Array.from((event.target as HTMLInputElement).files ?? [])); }} /> - + ); } diff --git a/apps/web/src/components/LiveProjectWorkspace.tsx b/apps/web/src/components/LiveProjectWorkspace.tsx index b426686f..899b90ed 100644 --- a/apps/web/src/components/LiveProjectWorkspace.tsx +++ b/apps/web/src/components/LiveProjectWorkspace.tsx @@ -1,7 +1,7 @@ import { message } from "../i18n/formatMessages"; import { t, useTranslation } from "../i18n/locale"; import { sentenceGap } from "../i18n/sentences"; -import { Button, Callout, Icon, InputGroup, Tab, Tabs, Tag } from "@blueprintjs/core"; +import { Button, Callout, InputGroup, Tab, Tabs, Tag } from "@blueprintjs/core"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode, type RefObject } from "react"; import { flushSync } from "react-dom"; @@ -27,7 +27,6 @@ import { workspaceSetupIssue, workspaceSetupIssueText, type WorkspaceSetupIssue import { LanguageSettingsCard } from "./LanguageSettingsCard"; import { useResearchSession } from "./useResearchSession"; import { useReadAccessProtection } from "./useReadAccessProtection"; -import { eyebrowLabel } from "./statusLabels"; import { WorkspaceErrorBoundary } from "./WorkspaceErrorBoundary"; import { ConversationTimeline } from "./ConversationTimeline"; import { ArchivedProjects } from "./ArchivedProjects"; @@ -169,8 +168,8 @@ export function ProjectLanding({ projects, loading = false, contextRestoreIssue {sentenceGap()}{t("workspace.restoreScope")}; const setupNotice = setupIssue && {workspaceSetupIssueText(setupIssue)}{sentenceGap()}{t("workspace.existingRead")}; if (creating) return <>{setupNotice}{restoreNotice}; - return
{setupNotice}{restoreNotice}

{eyebrowLabel("RESEARCH WORKSPACE")}

{t("workspace.continueHeading")}

{t("workspace.openOrCreate")}

-

{t("workspace.projects")}

+ return
{setupNotice}{restoreNotice} +

{t("workspace.projects")}

{loading&&

{t("workspace.loadingProjects")}

} {projects.map(project=>)}
diff --git a/apps/web/src/components/LiveProjectWorkspaceRestart.test.tsx b/apps/web/src/components/LiveProjectWorkspaceRestart.test.tsx index 38f2a257..9d6d3454 100644 --- a/apps/web/src/components/LiveProjectWorkspaceRestart.test.tsx +++ b/apps/web/src/components/LiveProjectWorkspaceRestart.test.tsx @@ -542,6 +542,20 @@ it("opens the project at its most recently updated work and shows the project cu expect(container.textContent).not.toMatch(GATE_CHECKING); }); +it("starts the home screen with the list of projects under a plain title, without a headline about the product", async () => { + await mount(); await settle(() => Boolean(container.querySelector(".project-home .project-home-item"))); + const home = container.querySelector(".project-home")!; + expect([...home.querySelectorAll("h1")].map(heading => heading.textContent)).toEqual(["프로젝트"]); + // the list is the first thing on the page and is named by that title + const list = home.querySelector("section.project-home-list")!; + expect(list.getAttribute("aria-labelledby")).toBe(home.querySelector("h1")!.id); + expect(home.querySelector(".eyebrow")).toBeNull(); + expect(home.querySelector(".workspace-heading")).toBeNull(); + expect(home.textContent).not.toContain("연구를 이어가거나"); + // one filled button: the way to a new project + expect([...home.querySelectorAll("button.bp6-intent-primary")].map(button => button.textContent)).toEqual(["새 프로젝트"]); +}); + it("keeps the new-session screen when the user chooses 새 대화 after opening a project", async () => { fixture.executionReady = true; await mount(); await settle(() => Boolean(container.querySelector(".project-button"))); diff --git a/apps/web/src/components/ModelSettings.test.tsx b/apps/web/src/components/ModelSettings.test.tsx index 243be6e5..72876da5 100644 --- a/apps/web/src/components/ModelSettings.test.tsx +++ b/apps/web/src/components/ModelSettings.test.tsx @@ -393,7 +393,8 @@ it("shows this project's settings on the project tab and this computer's from th await openPage("설정"); const titles = () => [...container.querySelectorAll(".workspace-content h2")].map(item => item.textContent); expect(container.querySelector(".workspace-heading h1")?.textContent).toBe("프로젝트 설정"); - expect(container.querySelector(".workspace-heading .eyebrow")?.textContent).toBe("이 프로젝트"); + // the title says the scope once; no small label above it says it again + expect(container.querySelector(".workspace-heading .eyebrow")).toBeNull(); const projectTitles = titles(); expect(projectTitles).toContain("모델 기본값"); expect(projectTitles).not.toContain("모델 연결"); @@ -401,7 +402,7 @@ it("shows this project's settings on the project tab and this computer's from th await act(async () => { ([...container.querySelectorAll(".sidebar-bottom button")].find(item => item.textContent === "이 컴퓨터 설정") as HTMLButtonElement).click(); }); await flush(); await flush(); expect(container.querySelector(".workspace-heading h1")?.textContent).toBe("이 컴퓨터 설정"); - expect(container.querySelector(".workspace-heading .eyebrow")?.textContent).toBe("이 컴퓨터"); + expect(container.querySelector(".workspace-heading .eyebrow")).toBeNull(); const computerTitles = titles(); expect(computerTitles).toContain("모델 연결"); expect(computerTitles).toContain("언어"); diff --git a/apps/web/src/components/ModelSettings.tsx b/apps/web/src/components/ModelSettings.tsx index 64c523e3..1ef3ba02 100644 --- a/apps/web/src/components/ModelSettings.tsx +++ b/apps/web/src/components/ModelSettings.tsx @@ -1,7 +1,8 @@ import { message } from "../i18n/formatMessages"; import { t, useTranslation } from "../i18n/locale"; import { joinSentences, sentenceGap } from "../i18n/sentences"; -import { Button, HTMLSelect } from "@blueprintjs/core"; +import { Button } from "@blueprintjs/core"; +import { Select } from "./Select"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { rpc } from "../api/rpcClient"; @@ -135,16 +136,16 @@ export function ModelSettings({ projectId, threadId, selection, onSelect, onSave : inherited.error ? {t("model.parentFailed")} : {message("inheritedSelection", inheritedLabel ?? t("model.noProjectModel"))})} {inheritsProject && data?.availability !== "UNAVAILABLE" && {t("model.inheritsProject")}} - + {!compact &&
); } - diff --git a/apps/web/src/components/ProjectSessionPages.tsx b/apps/web/src/components/ProjectSessionPages.tsx index aea06e23..a56bf45f 100644 --- a/apps/web/src/components/ProjectSessionPages.tsx +++ b/apps/web/src/components/ProjectSessionPages.tsx @@ -96,7 +96,7 @@ function ProjectModelDefault({ projectId }: { projectId: string }) { function ProjectSettingsPage({ project, threadId, workspaceId, session, capturedEpoch, status, onPage, onOpenCatalog, modelConnected }: SettingsProps) { return <>
-

{t("settings.thisProject")}

{t("settings.projectTitle")}

{t("settings.projectNotice")}

+

{t("settings.projectTitle")}

{t("settings.projectNotice")}

@@ -127,7 +127,7 @@ export function ComputerSettingsPage({ credentialProjectId, workspaceId, storage }) { return <>
-

{eyebrowLabel("LOCAL WORKSPACE")}

{t("settings.computerTitle")}

{t("settings.computerNotice")}

+

{t("settings.computerTitle")}

{t("settings.computerNotice")}

{t("workspace.connectModel")}

diff --git a/apps/web/src/components/ResearchResultCard.tsx b/apps/web/src/components/ResearchResultCard.tsx index 9a7a4a4d..e6c8f0bd 100644 --- a/apps/web/src/components/ResearchResultCard.tsx +++ b/apps/web/src/components/ResearchResultCard.tsx @@ -33,7 +33,7 @@ import { ResultHoldDetails } from "./ResultHoldDetails"; * One research result, read top to bottom: state tags next to the heading, the answer, the detail buttons, then why * the answer is in that state (folding), the alternative explanations and the next actions. */ -export function ResearchResultCard({ result, state, unavailable, error, failure, terminalReason, onDetail, onRetry, onResume, resumeCompleted, resuming, historySelection, currentness, progressSummary, coverageMatrix, nextUserAction, decisionDelta, usageLine, executionHold, projectId, holdCauseRef, onOpenTrace, hypothesisLinks, linkDistribution, hasSavedResult = false, autoRetryOff, onOpenSettings }: { +export function ResearchResultCard({ result, state, unavailable, error, failure, terminalReason, onDetail, onRetry, onResume, resumeCompleted, resuming, historySelection, currentness, progressSummary, coverageMatrix, nextUserAction, decisionDelta, usageLine, executionHold, projectId, holdCauseRef, onOpenTrace, hypothesisLinks, linkDistribution, hasSavedResult = false, autoRetryOff, onOpenSettings, earlierRound }: { result: Record | null; state: string; unavailable?: string; error?:ExecutionError | null; failure?:ResearchFailure | null; terminalReason?:string | null; onDetail: (detail: ResearchDetail) => void; onRetry?: () => void; /** Continue the interrupted run from where it stopped; only offered with the number of stages it had finished. */ onResume?: () => void; resumeCompleted?: number; resuming?: boolean; historySelection?: HistorySelection; currentness?: Currentness; @@ -47,6 +47,8 @@ export function ResearchResultCard({ result, state, unavailable, error, failure, hasSavedResult?: boolean; /** The project's automatic retry of an interrupted model call is off (read by the timeline, which has the query client). */ autoRetryOff?: boolean; onOpenSettings?: () => void; + /** A saved round before the latest one. */ + earlierRound?: boolean; }) { useTranslation(); const languageCheck = objectValue(result?.hypothesis_language_check); @@ -80,7 +82,7 @@ export function ResearchResultCard({ result, state, unavailable, error, failure, ] as const).map(([kind,label])=>)}
{executionHold && projectId && } - + {Object.hasOwn(objectValue(result.portfolio), "hypotheses") &&

{t("result.alternatives")}

{causeUnconfirmed(result) && } {hypotheses.length ? hypotheses.map((h,index)=>

{textValue(h.statement)}

{textValue(h.uncertainty)&&{textValue(h.uncertainty)}}{projectId&&item.hypothesis_id===textValue(h.hypothesis_id))} all={hypothesisLinks} distribution={linkDistribution}/>}
) :

{t("result.noHypotheses")}

} diff --git a/apps/web/src/components/ResourcesPage.test.tsx b/apps/web/src/components/ResourcesPage.test.tsx index 1720b8ad..cda754f5 100644 --- a/apps/web/src/components/ResourcesPage.test.tsx +++ b/apps/web/src/components/ResourcesPage.test.tsx @@ -110,4 +110,3 @@ it("offers no way to the setting where this screen cannot change it (hosted revi expect(web.textContent).not.toContain("허용하면 여기서"); expect(web.querySelectorAll("button")).toHaveLength(0); }); - diff --git a/apps/web/src/components/ResultCardOnce.test.tsx b/apps/web/src/components/ResultCardOnce.test.tsx index b693c43c..cd870fb6 100644 --- a/apps/web/src/components/ResultCardOnce.test.tsx +++ b/apps/web/src/components/ResultCardOnce.test.tsx @@ -112,6 +112,14 @@ it("says the missing answer checks in the body, because no header tag can", () = expect(html).not.toContain("답변 점검 0개"); }); +it("says of an earlier round that its answer checks are not shown here, not that they have not arrived yet", () => { + const html = render(withOpen("PARTIAL_HOLD"), { earlierRound: true }); + expect(html).toContain("답할 수 있는 범위: 이 회차의 답변 점검 항목은 여기서 볼 수 없습니다. 최신 회차의 답에서만 보입니다."); + expect(html).not.toContain("아직 읽지 못했습니다"); + // what the assessment left open is still said + for (const reason of OPEN_KO) expect(html).toContain(reason); +}); + const usageStatus = (total: number | null, part: number | null) => ({ request: { operation_id: "op" }, completed_stages: [], result_usage: { op: { operation_id: "op", calls: 2, input_tokens: null, output_tokens: null, total_tokens: total, cached_input_tokens: null, diff --git a/apps/web/src/components/ResultHoldDetails.tsx b/apps/web/src/components/ResultHoldDetails.tsx index e28c9604..3621e5f6 100644 --- a/apps/web/src/components/ResultHoldDetails.tsx +++ b/apps/web/src/components/ResultHoldDetails.tsx @@ -20,9 +20,11 @@ import type { ResearchDetail } from "./ResearchResultCard"; * check, and one folded block of technical details for all of it. One folding block under the answer; the * header tags keep the status and the counts visible when it is folded, so they are not repeated inside. */ -export function ResultHoldDetails({ result, historySelection, coverageMatrix, progressSummary, nextUserAction, decisionDelta, onDetail }: { +export function ResultHoldDetails({ result, historySelection, coverageMatrix, progressSummary, nextUserAction, decisionDelta, earlierRound, onDetail }: { result: Record; historySelection?: HistorySelection; coverageMatrix?: CoverageMatrix | null; progressSummary?: UserProgressSummary | null; nextUserAction?: NextUserAction | null; decisionDelta?: DecisionDelta | null; + /** A saved round before the latest one: its answer checks are not sent, and never will be. */ + earlierRound?: boolean; onDetail: (detail: ResearchDetail) => void; }) { const language = useResultLanguage(); @@ -39,7 +41,7 @@ export function ResultHoldDetails({ result, historySelection, coverageMatrix, pr ]; // Open by default (the mock-up R1 shows it open); the header tags keep the counts visible once the reader folds it. return - {status && }/>} + {status && }/>} onDetail({ kind: "evidence", result, historySelection }) : undefined}/> {gaps.length > 0 && ( diff --git a/apps/web/src/components/Select.test.tsx b/apps/web/src/components/Select.test.tsx new file mode 100644 index 00000000..116fcf9f --- /dev/null +++ b/apps/web/src/components/Select.test.tsx @@ -0,0 +1,27 @@ +import { renderToStaticMarkup } from "react-dom/server"; +import { readdirSync, readFileSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { expect, it } from "vitest"; +import { Select } from "./Select"; + +it("draws a select whose arrow has no English tooltip and is hidden from a screen reader", () => { + const html = renderToStaticMarkup(

{t("sourceTime.notice")}

{assessment.cutoff_state !== "PROHIBITED_CONTEXT" && <> diff --git a/apps/web/src/components/WorkspaceTopbar.tsx b/apps/web/src/components/WorkspaceTopbar.tsx index d21fcfcc..b2304e12 100644 --- a/apps/web/src/components/WorkspaceTopbar.tsx +++ b/apps/web/src/components/WorkspaceTopbar.tsx @@ -86,4 +86,3 @@ export function WorkspaceTopbar({ drawer, testMode, projectLabel, threadLabel, c ); } - diff --git a/apps/web/src/components/history/ResearchHistoryWorkspace.tsx b/apps/web/src/components/history/ResearchHistoryWorkspace.tsx index c031aa49..6aa2a837 100644 --- a/apps/web/src/components/history/ResearchHistoryWorkspace.tsx +++ b/apps/web/src/components/history/ResearchHistoryWorkspace.tsx @@ -8,7 +8,6 @@ import { selectionKey, type HistoryKind, type HistoryScope, type HistorySelectio import { HistoryDetail } from "./HistoryDetail"; import { HistoryTimeline } from "./HistoryTimeline"; import { Disclosure } from "../Disclosure"; -import { eyebrowLabel } from "../statusLabels"; import { ProjectReviewEmptyNote, ProjectReviewList } from "./ProjectReviewList"; import { DecisionDeltaComparison } from "./DecisionDeltaComparison"; import { useReadAccessProtection } from "../useReadAccessProtection"; @@ -118,7 +117,7 @@ export function ResearchHistoryWorkspace({ projectId, threadId, requestDigest, i return
{import.meta.env.VITE_THOTH_TEST_MODE === "true" && {t("historyWorkspace.testMode")}} {/* The panel has its own title ("변경 이력"); a second, different one inside it read as another feature. */} - {!compact &&

{eyebrowLabel("RESEARCH HISTORY")}

{kinds?.length === 1 && kinds[0] === "MEMORY" ? t("result.memory") : t("workspace.history")}

{t("historyWorkspace.preserve")}

+ {!compact &&

{kinds?.length === 1 && kinds[0] === "MEMORY" ? t("result.memory") : t("workspace.history")}

{t("historyWorkspace.preserve")}

{!initialSelection && !requestDigest && threadId &&
}
} {compact && initialSelection diff --git a/apps/web/src/components/statusLabels.test.tsx b/apps/web/src/components/statusLabels.test.tsx index 18433e6d..becf2670 100644 --- a/apps/web/src/components/statusLabels.test.tsx +++ b/apps/web/src/components/statusLabels.test.tsx @@ -31,7 +31,7 @@ it("names answer status, provider routes and English eyebrows", () => { expect(answerStatusLabel(undefined)).toBeNull(); expect(providerLabel("codex-oauth")).toBe("ChatGPT 계정"); expect(providerLabel("mystery-route")).toBe("mystery-route"); - expect(eyebrowLabel("EVIDENCE")).toBe("근거"); + expect(eyebrowLabel("LOCAL WORKSPACE")).toBe("이 컴퓨터"); expect(eyebrowLabel("SOMETHING ELSE")).toBe("SOMETHING ELSE"); expect(reversibilityLabel("PARTIAL")).toBe("일부만 되돌릴 수 있음"); expect(actionStateLabel("APPROVED")).toBe("승인됨"); diff --git a/apps/web/src/components/useNavigationDrawer.ts b/apps/web/src/components/useNavigationDrawer.ts index f893979e..21521ab7 100644 --- a/apps/web/src/components/useNavigationDrawer.ts +++ b/apps/web/src/components/useNavigationDrawer.ts @@ -60,4 +60,3 @@ export function useNavigationDrawer() { } export type NavigationDrawer = ReturnType; - diff --git a/apps/web/src/i18n/apiNoticesMessages.ts b/apps/web/src/i18n/apiNoticesMessages.ts index 6c287017..1e5c1eaf 100644 --- a/apps/web/src/i18n/apiNoticesMessages.ts +++ b/apps/web/src/i18n/apiNoticesMessages.ts @@ -115,6 +115,10 @@ export const apiNoticesMessages = { "ko": "답변 점검 항목 정보를 아직 읽지 못했습니다", "en": "Answer-check information is not available yet" }, + "answerHold.coverageEarlier": { + "ko": "이 회차의 답변 점검 항목은 여기서 볼 수 없습니다. 최신 회차의 답에서만 보입니다.", + "en": "The answer checks of this turn are not shown here. They are shown for the latest turn only." + }, "approvalView.approved": { "ko": "승인됨 · 이 내용으로 유효", "en": "Approved · valid for this content" diff --git a/apps/web/src/i18n/countFormatMessages.ts b/apps/web/src/i18n/countFormatMessages.ts index 72a94382..48655d22 100644 --- a/apps/web/src/i18n/countFormatMessages.ts +++ b/apps/web/src/i18n/countFormatMessages.ts @@ -27,7 +27,7 @@ export const countFormats = { otherItemsChanged:{ko:(n:number)=>`그 밖의 항목 ${n}개가 바뀌었습니다.`,en:(n:number)=>`${n} other ${n===1?"item changed":"items changed"}.`, "zh-Hans": (n:number)=>`其他 ${n} 项发生了变化。`, "zh-Hant": (n:number)=>`其他 ${n} 項發生了變化。`, ja: (n:number)=>`そのほかの項目 ${n} 件が変更されました。`}, observation:{ko:(text:string,refs:string)=>`관찰: ${text}${refs?` (근거 참조 ${refs})`:""}`,en:(text:string,refs:string)=>`Observation: ${text}${refs?` (evidence references: ${refs})`:""}`, "zh-Hans": (text:string,refs:string)=>`观察:${text}${refs?` (证据引用:${refs})`:""}`, "zh-Hant": (text:string,refs:string)=>`觀察:${text}${refs?` (證據引用:${refs})`:""}`, ja: (text:string,refs:string)=>`観察:${text}${refs?`(根拠の参照 ${refs})`:""}`}, pastTest:{ko:(state:string)=>`이전 시험 결과: ${state}`,en:(state:string)=>`Earlier test result: ${state}`, "zh-Hans": (state:string)=>`较早的测试结果:${state}`, "zh-Hant": (state:string)=>`較早的測試結果:${state}`, ja: (state:string)=>`以前のテスト結果:${state}`}, - pastExclusion:{ko:(state:string)=>`이전 조사에서 한 가설이 ${state}로 표시되었습니다`,en:(state:string)=>`A hypothesis in an earlier investigation was marked ${state}`, "zh-Hans": (state:string)=>`较早调查中的一个假设被标记为${state}`, "zh-Hant": (state:string)=>`較早調查中的一個假設被標記為${state}`, ja: (state:string)=>`以前の調査で、ある仮説が「${state}」と記録されました`}, + pastExclusion:{ko:(state:string)=>`이전 조사에서 한 가설의 표시: ${state}`,en:(state:string)=>`A hypothesis in an earlier investigation was marked ${state}`, "zh-Hans": (state:string)=>`较早调查中的一个假设被标记为${state}`, "zh-Hant": (state:string)=>`較早調查中的一個假設被標記為${state}`, ja: (state:string)=>`以前の調査で、ある仮説が「${state}」と記録されました`}, conditionScope:{ko:(label:string,scope:string)=>`${label}(조건 범위: ${scope})`,en:(label:string,scope:string)=>`${label} (condition scope: ${scope})`, "zh-Hans": (label:string,scope:string)=>`${label}(条件范围:${scope})`, "zh-Hant": (label:string,scope:string)=>`${label}(條件範圍:${scope})`, ja: (label:string,scope:string)=>`${label}(条件の範囲:${scope})`}, withheldLessons:{ko:(stale:number,refuted:number)=>`원본 기록은 남아 있습니다. 근거가 바뀌었거나 반대 결과가 나와 뺀 교훈은 보이지 않습니다(${[stale>0?"낡음 "+stale+"개":"",refuted>0?"반박됨 "+refuted+"개":""].filter(Boolean).join(" · ")}).`,en:(stale:number,refuted:number)=>`Original records are retained. Lessons excluded after evidence changes or contrary results are hidden (${[stale>0?stale+" stale":"",refuted>0?refuted+" refuted":""].filter(Boolean).join(" · ")}).`, "zh-Hans": (stale:number,refuted:number)=>`原始记录会保留。证据变化后或出现相反结果后被排除的经验教训不再显示(${[stale>0?stale+" 条过时":"",refuted>0?refuted+" 条被反驳":""].filter(Boolean).join(" · ")})。`, "zh-Hant": (stale:number,refuted:number)=>`原始記錄會保留。證據變化後或出現相反結果後被排除的經驗教訓不再顯示(${[stale>0?stale+" 條過時":"",refuted>0?refuted+" 條被反駁":""].filter(Boolean).join(" · ")})。`, ja: (stale:number,refuted:number)=>`元の記録は残っています。根拠が変わったり反対の結果が出たりして外された教訓は、表示されません(${[stale>0?"古い "+stale+" 件":"",refuted>0?"反証された "+refuted+" 件":""].filter(Boolean).join(" · ")})。`}, investigatedAt:{ko:(time:string)=>`${time} 조사`,en:(time:string)=>`Investigated ${time}`, "zh-Hans": (time:string)=>`${time} 调查`, "zh-Hant": (time:string)=>`${time} 調查`, ja: (time:string)=>`${time} の調査`}, diff --git a/apps/web/src/i18n/formatMessages.ts b/apps/web/src/i18n/formatMessages.ts index 8a4ae128..303bc8a9 100644 --- a/apps/web/src/i18n/formatMessages.ts +++ b/apps/web/src/i18n/formatMessages.ts @@ -29,7 +29,7 @@ const formats = { detachedSources: { ko: (count: number) => `연결을 해제한 자료 ${count}개`, en: (count: number) => `${count} disconnected ${count === 1 ? "source" : "sources"}`, "zh-Hans": (count: number) => `已断开连接的资料 ${count} 个`, "zh-Hant": (count: number) => `已中斷連線的資料 ${count} 個`, ja: (count: number) => `接続を解除した資料 ${count} 件` }, projectMenu: { ko: (name: string) => `${name} 프로젝트 메뉴`, en: (name: string) => `Project menu for ${name}`, "zh-Hans": (name: string)=>`${name} 的项目菜单`, "zh-Hant": (name: string)=>`${name} 的專案選單`, ja: (name: string)=>`${name} のプロジェクトメニュー` }, archiveProject: { - ko: (name: string) => `프로젝트 "${name}"을 보관할까요? 보관하면 읽기 전용이 되고 목록에서 숨겨집니다. 보관은 되돌릴 수 있습니다. '보관한 프로젝트 보기'에서 되살립니다.`, + ko: (name: string) => `"${name}" 프로젝트를 보관할까요? 보관하면 읽기 전용이 되고 목록에서 숨겨집니다. 보관은 되돌릴 수 있습니다. '보관한 프로젝트 보기'에서 되살립니다.`, en: (name: string) => `Archive project "${name}"? It becomes read-only and is hidden from the list. You can restore it from archived projects.`, "zh-Hans": (name: string)=>`要归档项目“${name}”吗?归档后它将变为只读,并从列表中隐藏。你可以在“已归档的项目”中将其恢复。`, "zh-Hant": (name: string)=>`要歸檔專案“${name}”嗎?歸檔後它將變為唯讀,並從列表中隱藏。你可以在“已歸檔的專案”中將其還原。`, ja: (name: string)=>`プロジェクト「${name}」をアーカイブしますか?アーカイブすると読み取り専用になり、一覧から非表示になります。「アーカイブ済みのプロジェクトを見る」から復元できます。`, }, cutoff: { ko: (value: string | null) => `기준시점 ${value}`, en: (value: string | null) => `Cutoff ${value}`, "zh-Hans": (value: string | null)=>`截止时间 ${value}`, "zh-Hant": (value: string | null)=>`截止時間 ${value}`, ja: (value: string | null)=>`基準時点 ${value}` }, diff --git a/apps/web/src/i18n/ja/apiNoticesMessagesJa.ts b/apps/web/src/i18n/ja/apiNoticesMessagesJa.ts index 5dc6af67..bce6e378 100644 --- a/apps/web/src/i18n/ja/apiNoticesMessagesJa.ts +++ b/apps/web/src/i18n/ja/apiNoticesMessagesJa.ts @@ -31,6 +31,7 @@ export const apiNoticesJa: ColumnOf = { "memorySwitch.label": "調査で記憶を使う", "answerHold.title": "確認された事実と根拠の用途", "answerHold.coverageMissing": "回答のチェック情報はまだありません", + "answerHold.coverageEarlier": "この回の回答チェック項目はここには表示されません。最新の回の回答にだけ表示されます。", "approvalView.approved": "承認済み · この内容に有効", "approvalView.pending": "承認待ち", "approvalView.rejected": "却下", diff --git a/apps/web/src/i18n/ja/sourceMessagesJa.ts b/apps/web/src/i18n/ja/sourceMessagesJa.ts index c787b6a7..69bb8173 100644 --- a/apps/web/src/i18n/ja/sourceMessagesJa.ts +++ b/apps/web/src/i18n/ja/sourceMessagesJa.ts @@ -6,7 +6,6 @@ export const sourceJa: ColumnOf = { "sources.fileUploadCancel": "アップロードをキャンセル", "sources.fileUploadCancelled": "アップロードをキャンセルしました。資料は接続されていません。", "sources.fileTitle": "資料ファイルを接続", - "sources.preserve": "元の文章、バージョン、位置を保持します", "sources.fileTypes": "HWPX、PDF、DOCX、XLSX、CSV、JSON、MD、TXT、YAML、HTML のファイルをアップロードし、その権威性と利用範囲を記録します。", "sources.hostedUpload": "アップロードしたファイルは、このレビュー用プロジェクトで使われます。", "sources.localUpload": "ファイルはこのコンピューターの THOTH ワークスペースに一時的に置かれます。", diff --git a/apps/web/src/i18n/ja/workspaceDetailMessagesJa.ts b/apps/web/src/i18n/ja/workspaceDetailMessagesJa.ts index e18e0f9c..9a07dedc 100644 --- a/apps/web/src/i18n/ja/workspaceDetailMessagesJa.ts +++ b/apps/web/src/i18n/ja/workspaceDetailMessagesJa.ts @@ -2,7 +2,6 @@ import type { ColumnOf } from "../withColumns"; import type { workspaceDetailMessages } from "../workspaceDetailMessages"; export const workspaceDetailJa: ColumnOf = { - "workspace.continueHeading": "調査を続けるか、新しい問題を始めてください。", "workspace.authorizedRead": "アクセス権限の範囲内で、保存済みの調査を引き続き読めます。", "workspace.readOnlyHold": "権限の範囲内で、保存済みの調査は引き続き利用できます。新しいモデルの実行と同意の変更は保留されています。", "workspace.resumeFailed": "調査を再開できませんでした。", diff --git a/apps/web/src/i18n/ja/workspaceMessagesJa.ts b/apps/web/src/i18n/ja/workspaceMessagesJa.ts index b8bfaa6c..6db2fc74 100644 --- a/apps/web/src/i18n/ja/workspaceMessagesJa.ts +++ b/apps/web/src/i18n/ja/workspaceMessagesJa.ts @@ -72,8 +72,6 @@ export const workspaceJa: ColumnOf = { "workspace.contextCorrupt": "最後のワークスペースの場所を読み込めませんでした。元の記録は削除されていません。", "workspace.contextUnavailable": "このブラウザから最後のワークスペースの場所を読み込めませんでした。ストレージへのアクセスとワークスペースの識別子を確認してください。", "workspace.backProjects": "プロジェクトの選択に戻る", - "workspace.openOrCreate": "既存のプロジェクトを開いて続けるか、新しい調査の文脈を作成してください。", - "workspace.continueProject": "続けるプロジェクト", "workspace.projects": "プロジェクト", "workspace.loadingProjects": "プロジェクトを読み込んでいます…", "workspace.openProject": "プロジェクトを開く", diff --git a/apps/web/src/i18n/sourceMessages.ts b/apps/web/src/i18n/sourceMessages.ts index 2c774c03..1ee6923f 100644 --- a/apps/web/src/i18n/sourceMessages.ts +++ b/apps/web/src/i18n/sourceMessages.ts @@ -12,10 +12,6 @@ export const sourceMessages = { "ko": "파일 자료 연결", "en": "Connect source files" }, - "sources.preserve": { - "ko": "원문 · 버전 · 위치 보존", - "en": "Preserve original text, versions, and locations" - }, "sources.fileTypes": { "ko": "HWPX · PDF · DOCX · XLSX · CSV · JSON · MD · TXT · YAML · HTML 파일을 올리고, 자료의 성격과 사용 범위를 함께 기록합니다.", "en": "Upload HWPX, PDF, DOCX, XLSX, CSV, JSON, MD, TXT, YAML, or HTML files and record their authority and usage scope." diff --git a/apps/web/src/i18n/statusChinese.ts b/apps/web/src/i18n/statusChinese.ts index e7327419..edba441d 100644 --- a/apps/web/src/i18n/statusChinese.ts +++ b/apps/web/src/i18n/statusChinese.ts @@ -25,9 +25,7 @@ export const statusChinese = { answerStatus: { PARTIAL_HOLD: "评估暂缓", ASSESSED_WITH_OPEN_CHECKS: "部分回答", ASSESSED_FOR_REQUEST: "回答完成" }, providers: { default: "默认连接", "codex-oauth": "ChatGPT 账户", "xai-oauth": "xAI 账户", "claude-oauth": "Claude 账户", "claude-code": "Claude Code 账户", openai: "OpenAI API 密钥", anthropic: "Anthropic API 密钥", xai: "xAI API 密钥" }, - eyebrows: { EVIDENCE: "证据", "AUTHORIZED SOURCES": "授权的资料", "RESEARCH HISTORY": "研究历史", - "RESEARCH WORKSPACE": "研究工作区", "LOCAL WORKSPACE": "这台电脑", "REVIEW WORKSPACE": "审查工作区", - "CAPABILITY WORKBENCH": "功能工作台", "CANONICAL RECORDS": "已保存的记录" }, + eyebrows: { "LOCAL WORKSPACE": "这台电脑", "REVIEW WORKSPACE": "审查工作区" }, reversibility: { FULL: "可逆", PARTIAL: "部分可逆", NONE: "不可逆" }, actionState: { APPROVED: "已批准", PARTIAL: "部分已执行", SUCCEEDED: "完成", FAILED: "失败", CANCELLED: "已取消" }, coverageTerms: { RESEARCH_GAP: "研究缺口", APPLIED: "已应用于评估", NOT_ASSESSED: "未评估", INCONCLUSIVE: "尚无定论", diff --git a/apps/web/src/i18n/statusEnglish.ts b/apps/web/src/i18n/statusEnglish.ts index c0e67e6b..a5d09fd3 100644 --- a/apps/web/src/i18n/statusEnglish.ts +++ b/apps/web/src/i18n/statusEnglish.ts @@ -25,9 +25,7 @@ export const statusEnglish = { answerStatus: { PARTIAL_HOLD: "Assessment on hold", ASSESSED_WITH_OPEN_CHECKS: "Partial answer", ASSESSED_FOR_REQUEST: "Answer complete" }, providers: { default: "Default connection", "codex-oauth": "ChatGPT account", "xai-oauth": "xAI account", "claude-oauth": "Claude account", "claude-code": "Claude Code account", openai: "OpenAI API key", anthropic: "Anthropic API key", xai: "xAI API key" }, - eyebrows: { EVIDENCE: "Evidence", "AUTHORIZED SOURCES": "Authorized sources", "RESEARCH HISTORY": "Research history", - "RESEARCH WORKSPACE": "Research workspace", "LOCAL WORKSPACE": "This computer", "REVIEW WORKSPACE": "Review workspace", - "CAPABILITY WORKBENCH": "Capability workbench", "CANONICAL RECORDS": "Saved records" }, + eyebrows: { "LOCAL WORKSPACE": "This computer", "REVIEW WORKSPACE": "Review workspace" }, reversibility: { FULL: "Reversible", PARTIAL: "Partially reversible", NONE: "Irreversible" }, actionState: { APPROVED: "Approved", PARTIAL: "Partially executed", SUCCEEDED: "Complete", FAILED: "Failed", CANCELLED: "Cancelled" }, coverageTerms: { RESEARCH_GAP: "Research gap", APPLIED: "Applied to the assessment", NOT_ASSESSED: "Not assessed", INCONCLUSIVE: "Inconclusive", diff --git a/apps/web/src/i18n/statusJapanese.ts b/apps/web/src/i18n/statusJapanese.ts index 3b5ad173..1564da64 100644 --- a/apps/web/src/i18n/statusJapanese.ts +++ b/apps/web/src/i18n/statusJapanese.ts @@ -25,9 +25,7 @@ export const statusJapanese = { answerStatus: { PARTIAL_HOLD: "判定は保留", ASSESSED_WITH_OPEN_CHECKS: "部分的な回答", ASSESSED_FOR_REQUEST: "回答完了" }, providers: { default: "既定の接続", "codex-oauth": "ChatGPT アカウント", "xai-oauth": "xAI アカウント", "claude-oauth": "Claude アカウント", "claude-code": "Claude Code アカウント", openai: "OpenAI API キー", anthropic: "Anthropic API キー", xai: "xAI API キー" }, - eyebrows: { EVIDENCE: "根拠", "AUTHORIZED SOURCES": "許可された資料", "RESEARCH HISTORY": "調査履歴", - "RESEARCH WORKSPACE": "調査ワークスペース", "LOCAL WORKSPACE": "このコンピューター", "REVIEW WORKSPACE": "レビュー用ワークスペース", - "CAPABILITY WORKBENCH": "機能ワークベンチ", "CANONICAL RECORDS": "保存された記録" }, + eyebrows: { "LOCAL WORKSPACE": "このコンピューター", "REVIEW WORKSPACE": "レビュー用ワークスペース" }, reversibility: { FULL: "元に戻せる", PARTIAL: "一部は元に戻せる", NONE: "元に戻せない" }, actionState: { APPROVED: "承認済み", PARTIAL: "一部実行済み", SUCCEEDED: "完了", FAILED: "失敗", CANCELLED: "キャンセル済み" }, coverageTerms: { RESEARCH_GAP: "調査の不足", APPLIED: "判定に反映済み", NOT_ASSESSED: "未評価", INCONCLUSIVE: "結論は出ていない", diff --git a/apps/web/src/i18n/statusResources.ts b/apps/web/src/i18n/statusResources.ts index 4d74b6b2..0c88adbf 100644 --- a/apps/web/src/i18n/statusResources.ts +++ b/apps/web/src/i18n/statusResources.ts @@ -53,9 +53,7 @@ providers: { "claude-code": "Claude Code 계정", openai: "OpenAI API 키", anthropic: "Anthropic API 키", xai: "xAI API 키", }, eyebrows: { - EVIDENCE: "근거", "AUTHORIZED SOURCES": "허용된 자료", "RESEARCH HISTORY": "연구 이력", - "RESEARCH WORKSPACE": "연구 작업 공간", "LOCAL WORKSPACE": "이 컴퓨터", "REVIEW WORKSPACE": "심사 작업 공간", - "CAPABILITY WORKBENCH": "기능 탐색", "CANONICAL RECORDS": "저장된 기록", + "LOCAL WORKSPACE": "이 컴퓨터", "REVIEW WORKSPACE": "심사 작업 공간", }, reversibility: { FULL: "되돌릴 수 있음", PARTIAL: "일부만 되돌릴 수 있음", NONE: "되돌릴 수 없음" }, actionState: { APPROVED: "승인됨", PARTIAL: "일부만 실행됨", SUCCEEDED: "완료", FAILED: "실패", CANCELLED: "취소됨" }, diff --git a/apps/web/src/i18n/statusTraditional.ts b/apps/web/src/i18n/statusTraditional.ts index 624e1895..4a323965 100644 --- a/apps/web/src/i18n/statusTraditional.ts +++ b/apps/web/src/i18n/statusTraditional.ts @@ -25,9 +25,7 @@ export const statusTraditional = { answerStatus: { PARTIAL_HOLD: "評估暫緩", ASSESSED_WITH_OPEN_CHECKS: "部分回答", ASSESSED_FOR_REQUEST: "回答完成" }, providers: { default: "預設連線", "codex-oauth": "ChatGPT 帳戶", "xai-oauth": "xAI 帳戶", "claude-oauth": "Claude 帳戶", "claude-code": "Claude Code 帳戶", openai: "OpenAI API 金鑰", anthropic: "Anthropic API 金鑰", xai: "xAI API 金鑰" }, - eyebrows: { EVIDENCE: "證據", "AUTHORIZED SOURCES": "授權的資料", "RESEARCH HISTORY": "研究歷史", - "RESEARCH WORKSPACE": "研究工作區", "LOCAL WORKSPACE": "這臺電腦", "REVIEW WORKSPACE": "審查工作區", - "CAPABILITY WORKBENCH": "功能工作臺", "CANONICAL RECORDS": "已儲存的記錄" }, + eyebrows: { "LOCAL WORKSPACE": "這臺電腦", "REVIEW WORKSPACE": "審查工作區" }, reversibility: { FULL: "可逆", PARTIAL: "部分可逆", NONE: "不可逆" }, actionState: { APPROVED: "已批准", PARTIAL: "部分已執行", SUCCEEDED: "完成", FAILED: "失敗", CANCELLED: "已取消" }, coverageTerms: { RESEARCH_GAP: "研究缺口", APPLIED: "已應用於評估", NOT_ASSESSED: "未評估", INCONCLUSIVE: "尚無定論", diff --git a/apps/web/src/i18n/wordingRules.test.ts b/apps/web/src/i18n/wordingRules.test.ts index af8202f5..e3c7ca3a 100644 --- a/apps/web/src/i18n/wordingRules.test.ts +++ b/apps/web/src/i18n/wordingRules.test.ts @@ -46,6 +46,26 @@ it("calls the kind of a source by one Korean word in the status labels too", () expect(labels).not.toContain("권위"); }); +// 을/를, 이/가, 은/는, 와/과, 로/으로 depend on the last sound of the word before them. A sentence that puts one +// straight after an inserted name or label is wrong for half of the names ('"2차"을 보관할까요'). +const PARTICLE_AFTER_VALUE = /\$\{[^}]+\}["'”’]?(?:을|를|이|가|은|는|와|과|으로|로)(?=[ ,.?!`])/; +// The login button names one of three accounts (ChatGPT, Claude, xAI); read in Korean all three end in a vowel +// sound, so "로" is right for each. A fourth account whose name ends in a consonant needs "으로" and a new sentence. +const PARTICLE_IS_RIGHT_FOR_EVERY_VALUE = new Set(["loginButton"]); + +it("puts no Korean particle straight after an inserted value", () => { + const found: string[] = []; + for (const [key, value] of Object.entries(formatCatalog)) { + const source = (value.ko as (...values: never[]) => string).toString(); + if (!PARTICLE_IS_RIGHT_FOR_EVERY_VALUE.has(key) && PARTICLE_AFTER_VALUE.test(source.slice(source.indexOf("=>") + 2))) found.push(key); + } + expect(found).toEqual([]); + for (const account of ["ChatGPT", "Claude", "xAI"]) expect(message("loginButton", account, false)).toBe(account + "로 로그인"); + // the check sees what it is meant to see + expect(PARTICLE_AFTER_VALUE.test('`프로젝트 "${name}"을 보관할까요?`')).toBe(true); + expect(PARTICLE_AFTER_VALUE.test('`"${name}" 프로젝트를 보관할까요?`')).toBe(false); +}); + // Text between tags that is written in capitals or as a bare English word is not in the translation // lists, so every language would see it in English. The product name is the one that stays. const FIXED_ENGLISH = />\s*([A-Z][A-Za-z]+(?: [A-Za-z]+)*)\s* { expect(found).toEqual([]); }); +// A small label above a heading ("eyebrow") is used only where it says something the heading under it does not. +// One above every heading, repeating the heading or the open tab, is decoration (the audit's slop list, 2). +const SMALL_LABELS: Record = { + "FirstRunSetup.tsx": "the step of the first-run setup (1 / 2), and that an existing setup needs a look", + "ModelCredentialPanel.tsx": "what the list of account names under it is for", + "ProjectSessionPages.tsx": "that the page belongs to the review site, which its title does not say", +}; + +it("puts a small label above a heading only where the label has something of its own to say", () => { + const used = new Map(); + for (const path of screenSources(resolve(import.meta.dirname, ".."))) { + const count = readFileSync(path, "utf8").split('className="eyebrow"').length - 1; + if (count > 0) used.set(path.split(/[\\/]/).pop()!, count); + } + expect([...used.keys()].sort()).toEqual(Object.keys(SMALL_LABELS).sort()); + expect(used.get("FirstRunSetup.tsx")).toBe(2); + expect(used.get("ModelCredentialPanel.tsx")).toBe(1); + expect(used.get("ProjectSessionPages.tsx")).toBe(1); +}); + it("names the workspace and the project list in every screen language", () => { for (const key of ["brand.tagline", "nav.projects", "nav.workspaceRoot"] as const) { const row = messages[key]; diff --git a/apps/web/src/i18n/workspaceDetailMessages.ts b/apps/web/src/i18n/workspaceDetailMessages.ts index 0438873b..100babdd 100644 --- a/apps/web/src/i18n/workspaceDetailMessages.ts +++ b/apps/web/src/i18n/workspaceDetailMessages.ts @@ -1,8 +1,4 @@ export const workspaceDetailMessages = { - "workspace.continueHeading": { - "ko": "연구를 이어가거나 새 문제를 시작하세요.", - "en": "Continue your research or start a new problem." - }, "workspace.authorizedRead": { "ko": "권한 있는 저장 연구는 계속 읽을 수 있습니다.", "en": "You can still read saved research within your access permissions." diff --git a/apps/web/src/i18n/workspaceMessages.ts b/apps/web/src/i18n/workspaceMessages.ts index 80b93f23..03b82e40 100644 --- a/apps/web/src/i18n/workspaceMessages.ts +++ b/apps/web/src/i18n/workspaceMessages.ts @@ -258,14 +258,6 @@ export const workspaceMessages = { "ko": "프로젝트 선택으로 돌아가기", "en": "Back to project selection" }, - "workspace.openOrCreate": { - "ko": "기존 프로젝트를 열어 작업을 이어가거나, 새로운 연구 맥락을 만들 수 있습니다.", - "en": "Open an existing project to continue, or create a new research context." - }, - "workspace.continueProject": { - "ko": "이어갈 프로젝트", - "en": "Project to continue" - }, "workspace.projects": { "ko": "프로젝트", "en": "Projects" diff --git a/apps/web/src/i18n/zh/apiNoticesMessagesZh.ts b/apps/web/src/i18n/zh/apiNoticesMessagesZh.ts index fb8a1536..1ce8d94f 100644 --- a/apps/web/src/i18n/zh/apiNoticesMessagesZh.ts +++ b/apps/web/src/i18n/zh/apiNoticesMessagesZh.ts @@ -31,6 +31,7 @@ export const apiNoticesZh: ColumnOf = { "memorySwitch.label": "在研究中使用记忆", "answerHold.title": "已确认的事实与证据用途", "answerHold.coverageMissing": "暂时没有回答核查信息", + "answerHold.coverageEarlier": "这一轮的回答核查项目不在这里显示,只在最新一轮的回答中显示。", "approvalView.approved": "已批准 · 对此内容有效", "approvalView.pending": "等待批准", "approvalView.rejected": "已拒绝", diff --git a/apps/web/src/i18n/zh/sourceMessagesZh.ts b/apps/web/src/i18n/zh/sourceMessagesZh.ts index 0ff52bd6..8b3a2f5a 100644 --- a/apps/web/src/i18n/zh/sourceMessagesZh.ts +++ b/apps/web/src/i18n/zh/sourceMessagesZh.ts @@ -6,7 +6,6 @@ export const sourceZh: ColumnOf = { "sources.fileUploadCancel": "取消上传", "sources.fileUploadCancelled": "上传已取消。没有连接任何资料。", "sources.fileTitle": "连接资料文件", - "sources.preserve": "保留原文、版本和位置", "sources.fileTypes": "上传 HWPX、PDF、DOCX、XLSX、CSV、JSON、MD、TXT、YAML 或 HTML 文件,并记录其权威性和使用范围。", "sources.hostedUpload": "上传的文件用于此审查项目。", "sources.localUpload": "文件暂存在这台电脑的 THOTH 工作区中。", diff --git a/apps/web/src/i18n/zh/workspaceDetailMessagesZh.ts b/apps/web/src/i18n/zh/workspaceDetailMessagesZh.ts index 269d2a91..50430429 100644 --- a/apps/web/src/i18n/zh/workspaceDetailMessagesZh.ts +++ b/apps/web/src/i18n/zh/workspaceDetailMessagesZh.ts @@ -2,7 +2,6 @@ import type { ColumnOf } from "../withColumns"; import type { workspaceDetailMessages } from "../workspaceDetailMessages"; export const workspaceDetailZh: ColumnOf = { - "workspace.continueHeading": "继续你的研究,或开始一个新问题。", "workspace.authorizedRead": "在你的访问权限范围内,仍可阅读已保存的研究。", "workspace.readOnlyHold": "在你的权限范围内,已保存的研究仍可使用。新的模型运行和同意变更已暂缓。", "workspace.resumeFailed": "无法继续研究。", diff --git a/apps/web/src/i18n/zh/workspaceMessagesZh.ts b/apps/web/src/i18n/zh/workspaceMessagesZh.ts index b4fab945..3ae2f46c 100644 --- a/apps/web/src/i18n/zh/workspaceMessagesZh.ts +++ b/apps/web/src/i18n/zh/workspaceMessagesZh.ts @@ -72,8 +72,6 @@ export const workspaceZh: ColumnOf = { "workspace.contextCorrupt": "无法读取上一次的工作区位置。原始记录没有被删除。", "workspace.contextUnavailable": "无法在此浏览器中读取上一次的工作区位置。请检查存储访问权限和工作区标识。", "workspace.backProjects": "返回项目选择", - "workspace.openOrCreate": "打开已有的项目以继续,或创建新的研究上下文。", - "workspace.continueProject": "要继续的项目", "workspace.projects": "项目", "workspace.loadingProjects": "正在加载项目…", "workspace.openProject": "打开项目", diff --git a/apps/web/src/i18n/zhHant/apiNoticesMessagesZhHant.ts b/apps/web/src/i18n/zhHant/apiNoticesMessagesZhHant.ts index ec57cc8a..484ce367 100644 --- a/apps/web/src/i18n/zhHant/apiNoticesMessagesZhHant.ts +++ b/apps/web/src/i18n/zhHant/apiNoticesMessagesZhHant.ts @@ -31,6 +31,7 @@ export const apiNoticesZhHant: ColumnOf = { "memorySwitch.label": "在研究中使用記憶", "answerHold.title": "已確認的事實與證據用途", "answerHold.coverageMissing": "暫時沒有回答核查資訊", + "answerHold.coverageEarlier": "這一輪的回答核查項目不在這裡顯示,只在最新一輪的回答中顯示。", "approvalView.approved": "已批准 · 對此內容有效", "approvalView.pending": "等待批准", "approvalView.rejected": "已拒絕", diff --git a/apps/web/src/i18n/zhHant/sourceMessagesZhHant.ts b/apps/web/src/i18n/zhHant/sourceMessagesZhHant.ts index e4808335..589ec8b8 100644 --- a/apps/web/src/i18n/zhHant/sourceMessagesZhHant.ts +++ b/apps/web/src/i18n/zhHant/sourceMessagesZhHant.ts @@ -6,7 +6,6 @@ export const sourceZhHant: ColumnOf = { "sources.fileUploadCancel": "取消上傳", "sources.fileUploadCancelled": "上傳已取消。沒有連線任何資料。", "sources.fileTitle": "連線資料檔案", - "sources.preserve": "保留原文、版本和位置", "sources.fileTypes": "上傳 HWPX、PDF、DOCX、XLSX、CSV、JSON、MD、TXT、YAML 或 HTML 檔案,並記錄其權威性和使用範圍。", "sources.hostedUpload": "上傳的檔案用於此審查專案。", "sources.localUpload": "檔案暫存在這臺電腦的 THOTH 工作區中。", diff --git a/apps/web/src/i18n/zhHant/workspaceDetailMessagesZhHant.ts b/apps/web/src/i18n/zhHant/workspaceDetailMessagesZhHant.ts index b0ae8cc5..017bab46 100644 --- a/apps/web/src/i18n/zhHant/workspaceDetailMessagesZhHant.ts +++ b/apps/web/src/i18n/zhHant/workspaceDetailMessagesZhHant.ts @@ -2,7 +2,6 @@ import type { ColumnOf } from "../withColumns"; import type { workspaceDetailMessages } from "../workspaceDetailMessages"; export const workspaceDetailZhHant: ColumnOf = { - "workspace.continueHeading": "繼續你的研究,或開始一個新問題。", "workspace.authorizedRead": "在你的存取權限範圍內,仍可閱讀已儲存的研究。", "workspace.readOnlyHold": "在你的權限範圍內,已儲存的研究仍可使用。新的模型執行和同意變更已暫緩。", "workspace.resumeFailed": "無法繼續研究。", diff --git a/apps/web/src/i18n/zhHant/workspaceMessagesZhHant.ts b/apps/web/src/i18n/zhHant/workspaceMessagesZhHant.ts index 46e6011d..091e4d1b 100644 --- a/apps/web/src/i18n/zhHant/workspaceMessagesZhHant.ts +++ b/apps/web/src/i18n/zhHant/workspaceMessagesZhHant.ts @@ -72,8 +72,6 @@ export const workspaceZhHant: ColumnOf = { "workspace.contextCorrupt": "無法讀取上一次的工作區位置。原始記錄沒有被刪除。", "workspace.contextUnavailable": "無法在此瀏覽器中讀取上一次的工作區位置。請檢查儲存存取權限和工作區標識。", "workspace.backProjects": "返回專案選擇", - "workspace.openOrCreate": "開啟已有的專案以繼續,或建立新的研究上下文。", - "workspace.continueProject": "要繼續的專案", "workspace.projects": "專案", "workspace.loadingProjects": "正在載入專案…", "workspace.openProject": "開啟專案", diff --git a/apps/web/src/narrowLayoutRules.test.ts b/apps/web/src/narrowLayoutRules.test.ts index eda018de..d51ba2ea 100644 --- a/apps/web/src/narrowLayoutRules.test.ts +++ b/apps/web/src/narrowLayoutRules.test.ts @@ -109,3 +109,13 @@ it("never shortens the state tag of a provider on the first-run screen", () => { expect(head[0].body).toContain("flex-wrap: wrap"); }); +it("never shortens the label of where this copy runs: it takes a row of its own when the name leaves no room", () => { + // English and Japanese do not fit next to the name in the project list's head; the label used to be cut to "THIS CO…". + const brand = all.filter(rule => has(rule, ".workstation .brand")); + expect(brand).toHaveLength(1); + expect(brand[0].body).toContain("flex-wrap: wrap"); + const tag = all.filter(rule => has(rule, ".workstation .brand .bp6-tag") && declares(rule, "flex")); + expect(tag.map(rule => rule.body.match(/flex:\s*([^;]+);/)?.[1])).toEqual(["none"]); + // nothing pushes the label off its row or gives it a width to be cut to + expect(all.filter(rule => has(rule, ".workstation .brand .bp6-tag") && (declares(rule, "max-width") || declares(rule, "width") || declares(rule, "overflow")))).toEqual([]); +}); diff --git a/apps/web/src/typeScaleRules.test.ts b/apps/web/src/typeScaleRules.test.ts index 13383d97..d62fd0dd 100644 --- a/apps/web/src/typeScaleRules.test.ts +++ b/apps/web/src/typeScaleRules.test.ts @@ -34,4 +34,3 @@ it("sets no font size in a component's own style", () => { const found = files(SOURCE, ".tsx").filter(path => /fontSize\s*:/.test(readFileSync(path, "utf8"))); expect(found).toEqual([]); }); - diff --git a/apps/web/src/workstation.css b/apps/web/src/workstation.css index 7f261792..c24672a3 100644 --- a/apps/web/src/workstation.css +++ b/apps/web/src/workstation.css @@ -76,11 +76,14 @@ button:focus-visible, summary:focus-visible, a:focus-visible { outline: 2px soli .workstation { display: grid; grid-template-columns: var(--sidebar-width) minmax(0,1fr); grid-template-rows: 56px 49px minmax(0,1fr); min-height: 100vh; } .workstation.with-evidence { grid-template-columns: var(--sidebar-width) minmax(450px,1fr) var(--evidence-width); } .workstation .navigation-rail { grid-column: 1; grid-row: 1 / -1; z-index: 10; background: #182029; border-right: 1px solid var(--line-soft); padding: 20px 12px 12px; height: 100vh; position: sticky; top: 0; gap: 12px; } -.workstation .brand { padding: 0 0 15px; gap: 9px; } +/* The name and the label of where this copy runs share a row when both fit. In a language where they do not + (English, Japanese), the label takes a row of its own and is shown whole; it is never shortened. */ +.workstation .brand { padding: 0 0 15px; gap: 6px 9px; flex-wrap: wrap; justify-content: space-between; } +.workstation .brand-name { display: flex; align-items: center; gap: 9px; min-width: 0; } .workstation .brand-mark { width: 34px; height: 34px; background: #294568; color: #9cc8ff; border: 1px solid #3d638b; border-radius: var(--radius); } .workstation .brand strong { font-size: 15px; letter-spacing: .18em; } .workstation .brand span { font-size: var(--font-small); letter-spacing: .09em; } -.workstation .brand .bp6-tag { margin-left: auto; font-size: var(--font-small); } +.workstation .brand .bp6-tag { flex: none; font-size: var(--font-small); } .workstation .nav-section { margin: 5px 0 0; } .workstation .nav-section-heading { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; } .workstation .nav-label { color: var(--faint); margin: 0 0 0 8px; font-size: var(--font-small); letter-spacing: .13em; } @@ -664,6 +667,9 @@ button:focus-visible, summary:focus-visible, a:focus-visible { outline: 2px soli /* ---- New project ---- */ .project-create { max-width: 760px; } .start-page.project-create h1 { margin: 8px 0 6px; font-size: 27px; line-height: 1.3; } +/* The home screen starts with the list of projects under a plain title of the same size. */ +.start-page.project-home h1 { margin: 0; font-size: 27px; line-height: 1.3; } +.start-page.project-home .project-home-list { margin-top: 0; } .project-create > .muted { margin: 0 0 18px; font-size: 13px; } .project-create .onboarding-card > .bp6-button { margin-top: 4px; }