fix(electron): use bundled Chromium version in user agent#1129
Conversation
# Conflicts: # public/translations/ar/default.json # public/translations/bn/default.json # public/translations/cs/default.json # public/translations/da/default.json # public/translations/de/default.json # public/translations/el/default.json # public/translations/en/default.json # public/translations/es/default.json # public/translations/fa/default.json # public/translations/fi/default.json # public/translations/fil/default.json # public/translations/fr/default.json # public/translations/he/default.json # public/translations/hi/default.json # public/translations/hu/default.json # public/translations/id/default.json # public/translations/it/default.json # public/translations/ja/default.json # public/translations/ko/default.json # public/translations/mr/default.json # public/translations/nl/default.json # public/translations/no/default.json # public/translations/pl/default.json # public/translations/pt/default.json # public/translations/ro/default.json # public/translations/ru/default.json # public/translations/sq/default.json # public/translations/sv/default.json # public/translations/te/default.json # public/translations/th/default.json # public/translations/tr/default.json # public/translations/uk/default.json # public/translations/ur/default.json # public/translations/vi/default.json # public/translations/zh/default.json
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR implements hidden catalog threads—allowing users to block and selectively view hidden comments—while adding media load failure messaging throughout the UI. Hidden state is now backed by account data instead of local storage. The catalog can toggle between showing visible and hidden-only threads. Media load failures display inline with host-specific guidance and VPN tips. Supporting changes refactor Electron user-agent generation, extract PKC RPC setup, and simplify board blotter logic. Translations are updated across 40+ languages. ChangesHidden Catalog Threads with Media Failure Support
Electron and Server Infrastructure Updates
Multilingual UI Messaging Updates
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6c68732. Configure here.
| @@ -0,0 +1,16 @@ | |||
| export const startPkcRpcServer = async ({ PKCRpcModule, port, pkcOptions, authKey, isDev = false, logger = console }) => { | |||
| const pkcWebSocketServer = await PKCRpcModule.PKCWsServer({ port, pkcOptions, authKey }); | |||
There was a problem hiding this comment.
PKC options property renamed, may break RPC server
High Severity
The old code passed configuration to PKCWsServer as { port, pkc: defaultPkcOptions, authKey }, but the refactored startPkcRpcServer now passes it as { port, pkcOptions, authKey }. If the underlying @pkcprotocol/pkc-js/rpc library expects the property name pkc, the server will silently start without its data path or IPFS connection options, rendering the desktop app unable to load boards or comments. The test only validates the wrapper's passthrough against a mock, not the real library's expected API shape.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 6c68732. Configure here.
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (4)
src/hooks/use-hide.ts (1)
68-86: 💤 Low valueAsymmetric error recovery in
unhidevshide.
hidereverts the optimisticrememberHiddenCommentwhenblockCidrejects and the account state confirms still-not-hidden (Lines 58-64).unhidecallsforgetHiddenComment(cid)up-front (Line 73) but never re-remembers onunblockCidrejection — so a failed unblock leaveshidden === true(account state unchanged) with no in-memoryCommentfor the catalog to display untiluseCommentsre-fetches. Consider restoring the remembered comment on failure to mirrorhide's rollback, e.g.:♻️ Suggested rollback
- const unhide = useCallback(() => { + const unhide = useCallback(() => { if (!cid) { return; } forgetHiddenComment(cid); if (!getCurrentAccountHiddenState(cid)) { return; } void accountsStore .getState() .accountsActions.unblockCid(cid) .catch((error: unknown) => { + if (getCurrentAccountHiddenState(cid)) { + rememberHiddenComment(comment); + } if (shouldLogHideActionError(cid, false)) { console.error('Failed to unhide post', error); } }); - }, [cid, forgetHiddenComment]); + }, [cid, comment, forgetHiddenComment, rememberHiddenComment]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/use-hide.ts` around lines 68 - 86, The unhide function currently removes the optimistic in-memory entry via forgetHiddenComment(cid) before calling accountsStore.getState().accountsActions.unblockCid and does not restore it on failure; update unhide to mirror hide's rollback: after catching errors from unblockCid, check if getCurrentAccountHiddenState(cid) still indicates hidden and, if so, call rememberHiddenComment(cid) to re-create the in-memory comment (only when shouldLogHideActionError or the account state requires it), ensuring the optimistic removal is reverted on unblock failures; reference unhide, forgetHiddenComment, rememberHiddenComment, accountsStore.getState().accountsActions.unblockCid, shouldLogHideActionError, and getCurrentAccountHiddenState when making the change.src/hooks/use-hidden-catalog-threads.ts (1)
167-183: ⚡ Quick winDefault
candidateComments = []creates a new array every call, invalidating downstream memos.When callers omit
candidateComments, the destructuring default[]allocates a fresh array on every render, so thecandidateCommentListuseMemoat Lines 180-183 (which listscandidateCommentsin its deps) recomputes every render — cascading throughhiddenThreadCandidatesandhiddenCatalogThreads. Use a module-level stable empty array:♻️ Stable default reference
+const EMPTY_CANDIDATE_COMMENTS: readonly Comment[] = Object.freeze([]) as readonly Comment[]; + -const useHiddenCatalogThreads = ({ candidateComments = [], communityAddresses, sortType }: HiddenCatalogThreadsOptions): HiddenCatalogThreadsResult => { +const useHiddenCatalogThreads = ({ candidateComments = EMPTY_CANDIDATE_COMMENTS, communityAddresses, sortType }: HiddenCatalogThreadsOptions): HiddenCatalogThreadsResult => {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/use-hidden-catalog-threads.ts` around lines 167 - 183, The default parameter candidateComments = [] in useHiddenCatalogThreads creates a new array each render and forces downstream memos to recompute; fix by introducing a module-level stable empty array (e.g., STABLE_EMPTY_CANDIDATE_COMMENTS: Comment[] = []) and use that stable constant as the default for candidateComments (or fall back inside the function) so candidateCommentList's useMemo (which depends on candidateComments and computes mergeCommentsByCid([...])) sees a stable reference and won't retrigger unnecessarily.src/hooks/use-prune-hidden-catalog-threads.ts (1)
89-89: 💤 Low valueSelector subscribes to the full
communitiesPagesmap.
communitiesPagesStore((state) => state.communitiesPages)returns the entire pages map for all communities, so this hook re-runs whenever pages for any unrelated community update. Since downstream code only needs the entry keyed by the current community, narrow the selector — e.g. derive pages fromcommunityAddressinside the selector, or memoize percommunity.posts?.pageCids— to keep this hook's re-render scope tight. The current pattern is functionally correct but unnecessarily broad.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/use-prune-hidden-catalog-threads.ts` at line 89, The selector currently subscribes to the entire communitiesPages map via communitiesPagesStore((state) => state.communitiesPages); change it so the selector only returns the entry for the current community (use communityAddress inside the selector) or directly derive the specific value needed (e.g. community.posts?.pageCids) so the hook (usePruneHiddenCatalogThreads or the hook body) only re-runs when that community's pages change; update the selector call to access state.communitiesPages[communityAddress] or return the memoized pageCids for that community instead of the whole map.src/views/catalog/catalog.tsx (1)
397-409: ⚡ Quick winConsider adding inline comments to clarify the auto-hide state machine.
This effect implements non-trivial logic to automatically dismiss the hidden-threads view when threads disappear. The guard at Line 406 (
hadVisibleHiddenThreadsRef.current || !isLoadingHiddenCatalogThreads) handles two distinct cases:
- User toggled to hidden mode, saw threads, then they all disappeared
- User toggled to hidden mode while loading finished with zero results
A brief inline comment explaining this state machine would help future maintainers.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/views/catalog/catalog.tsx` around lines 397 - 409, The effect in useEffect controlling auto-hide of the hidden-threads panel is non-obvious; add a concise inline comment above this block explaining the state machine: describe that when requestedShowHiddenThreads is false we reset hadVisibleHiddenThreadsRef, when hiddenThreadsCount > 0 we mark hadVisibleHiddenThreadsRef.current = true, and when either hadVisibleHiddenThreadsRef is true (threads were visible but then disappeared) or loading finished with zero results (!isLoadingHiddenCatalogThreads) we clear the shown scope via setShownHiddenThreadsScopeKey(null); reference the variables useEffect, requestedShowHiddenThreads, hadVisibleHiddenThreadsRef, hiddenThreadsCount, isLoadingHiddenCatalogThreads, and setShownHiddenThreadsScopeKey in the comment so future maintainers understand the two distinct cases handled.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@electron/start-pkc-rpc-core.js`:
- Line 6: The log currently prints the full secret authKey (logger.log in
start-pkc-rpc-core.js), which leaks credentials; change the logging in the PKC
RPC startup to avoid printing authKey in plaintext—log only non-sensitive info
such as the port and a redacted or hashed representation of authKey (e.g., mask
all but last 4 chars or log "authKey=REDACTED"/its SHA256) while keeping the
existing message context so callers can still see the service is listening
without exposing the secret.
In `@public/translations/ar/default.json`:
- Line 377: Replace the English string for the translation key
"enable_pure_p2p_tip" with an Arabic translation so the UI is fully localized
for Arabic users; update the value of "enable_pure_p2p_tip" from "Use a browser
P2P node instead of gateway compatibility mode." to an appropriate Arabic text
(for example: "استخدم عقدة P2P في المتصفح بدلاً من وضع التوافق مع البوابة.")
ensuring the JSON string remains valid.
In `@public/translations/en/default.json`:
- Line 383: Replace the ungrammatical value for the "app_is_up_to_date" key:
change the string "you're on latest" to a properly capitalized and punctuated
phrase such as "You're on the latest version." in the JSON so the translation
reads correctly.
In `@public/translations/ro/default.json`:
- Line 377: The translation key "enable_pure_p2p_tip" is still in English in the
Romanian locale; replace the English value with an appropriate Romanian
translation for that key in public/translations/ro/default.json and apply the
corresponding translated text to the same key ("enable_pure_p2p_tip") in the
other locales mentioned (ru, sq, sv, te, th, tr, uk) so the UI is consistent
across languages; ensure you only change the value string (keep the JSON key
intact and maintain valid JSON quoting/commas).
In `@public/translations/ur/default.json`:
- Line 377: The key "enable_pure_p2p_tip" currently has an English string;
replace its value with an Urdu translation so the Urdu locale is consistent —
update the JSON value for "enable_pure_p2p_tip" to an appropriate Urdu phrase
such as "گیٹ وے کمپٹیبیلٹی موڈ کی بجائے براؤزر P2P نوڈ استعمال کریں۔" ensuring
proper JSON quoting and punctuation for the existing "enable_pure_p2p_tip"
entry.
In `@public/translations/vi/default.json`:
- Line 377: Replace the English value for the translation key
"enable_pure_p2p_tip" with a Vietnamese translation so the locale isn't mixed;
update the value for "enable_pure_p2p_tip" (in
public/translations/vi/default.json) to a Vietnamese string such as "Sử dụng nút
P2P trên trình duyệt thay vì chế độ tương thích gateway." to match the rest of
the locale.
In `@public/translations/zh/default.json`:
- Line 377: The translation for the key "enable_pure_p2p_tip" is still in
English; replace the English value with a Chinese localized string while keeping
the key intact (e.g., "使用浏览器 P2P 节点,而不是网关兼容模式。") so the UI language is
consistent in this section.
In `@src/components/comment-media/comment-media.module.css`:
- Around line 194-204: The .mediaLoadFailureStatus rule uses the deprecated clip
property; replace the clip: rect(0 0 0 0); declaration in the
.mediaLoadFailureStatus CSS rule with a modern clip-path equivalent (e.g.,
clip-path: inset(0 0 0 0);) and include a vendor-prefixed fallback
(-webkit-clip-path) to preserve behavior across browsers while keeping the rest
of the visually-hidden declarations intact.
In `@src/components/comment-media/comment-media.tsx`:
- Around line 388-391: handleLoad currently always calls
onMediaLoadFailureChange(undefined) even when failedUrl isn't cleared, causing
parent/component desync; update handleLoad so it uses the functional updater
result to determine if failedUrl was actually changed (i.e., check whether
currentFailedUrl === url) and only invoke onMediaLoadFailureChange(undefined)
when you actually clear the failedUrl; reference the handleLoad function,
setFailedUrl updater, failedUrl state, onMediaLoadFailureChange callback, and
url prop when implementing this conditional notification.
In `@src/hooks/use-prune-hidden-catalog-threads.ts`:
- Around line 125-149: The effect currently requeues failed unblockCid calls
immediately because pendingPruneCidsRef.current.delete(cid) runs in .finally,
causing tight retry loops when accountsActions.unblockCid(cid) keeps rejecting;
change the logic in useEffect to track per-CID failure attempts (e.g.,
failureCountsRef keyed by cid) and only remove cid from pendingPruneCidsRef on
success or after a short cooldown/after N failures, incrementing
failureCountsRef[cid] on rejection and skipping retries when count >= N; also
reset failureCountsRef when activeAccountId or communityAddress (or whatever
drives removedHiddenThreadCids) changes so retries resume for new contexts.
Ensure you update the handlers around accountsActions.unblockCid, the .catch and
.finally behavior, and the dependency list (useEffect) references for resets.
---
Nitpick comments:
In `@src/hooks/use-hidden-catalog-threads.ts`:
- Around line 167-183: The default parameter candidateComments = [] in
useHiddenCatalogThreads creates a new array each render and forces downstream
memos to recompute; fix by introducing a module-level stable empty array (e.g.,
STABLE_EMPTY_CANDIDATE_COMMENTS: Comment[] = []) and use that stable constant as
the default for candidateComments (or fall back inside the function) so
candidateCommentList's useMemo (which depends on candidateComments and computes
mergeCommentsByCid([...])) sees a stable reference and won't retrigger
unnecessarily.
In `@src/hooks/use-hide.ts`:
- Around line 68-86: The unhide function currently removes the optimistic
in-memory entry via forgetHiddenComment(cid) before calling
accountsStore.getState().accountsActions.unblockCid and does not restore it on
failure; update unhide to mirror hide's rollback: after catching errors from
unblockCid, check if getCurrentAccountHiddenState(cid) still indicates hidden
and, if so, call rememberHiddenComment(cid) to re-create the in-memory comment
(only when shouldLogHideActionError or the account state requires it), ensuring
the optimistic removal is reverted on unblock failures; reference unhide,
forgetHiddenComment, rememberHiddenComment,
accountsStore.getState().accountsActions.unblockCid, shouldLogHideActionError,
and getCurrentAccountHiddenState when making the change.
In `@src/hooks/use-prune-hidden-catalog-threads.ts`:
- Line 89: The selector currently subscribes to the entire communitiesPages map
via communitiesPagesStore((state) => state.communitiesPages); change it so the
selector only returns the entry for the current community (use communityAddress
inside the selector) or directly derive the specific value needed (e.g.
community.posts?.pageCids) so the hook (usePruneHiddenCatalogThreads or the hook
body) only re-runs when that community's pages change; update the selector call
to access state.communitiesPages[communityAddress] or return the memoized
pageCids for that community instead of the whole map.
In `@src/views/catalog/catalog.tsx`:
- Around line 397-409: The effect in useEffect controlling auto-hide of the
hidden-threads panel is non-obvious; add a concise inline comment above this
block explaining the state machine: describe that when
requestedShowHiddenThreads is false we reset hadVisibleHiddenThreadsRef, when
hiddenThreadsCount > 0 we mark hadVisibleHiddenThreadsRef.current = true, and
when either hadVisibleHiddenThreadsRef is true (threads were visible but then
disappeared) or loading finished with zero results
(!isLoadingHiddenCatalogThreads) we clear the shown scope via
setShownHiddenThreadsScopeKey(null); reference the variables useEffect,
requestedShowHiddenThreads, hadVisibleHiddenThreadsRef, hiddenThreadsCount,
isLoadingHiddenCatalogThreads, and setShownHiddenThreadsScopeKey in the comment
so future maintainers understand the two distinct cases handled.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 02d8fc07-3659-48a2-a861-5c37857cff7b
📒 Files selected for processing (67)
electron/main.jselectron/start-pkc-rpc-core.jselectron/start-pkc-rpc-core.test.jselectron/start-pkc-rpc.jselectron/user-agent.jselectron/user-agent.test.jspublic/translations/ar/default.jsonpublic/translations/bn/default.jsonpublic/translations/cs/default.jsonpublic/translations/da/default.jsonpublic/translations/de/default.jsonpublic/translations/el/default.jsonpublic/translations/en/default.jsonpublic/translations/es/default.jsonpublic/translations/fa/default.jsonpublic/translations/fi/default.jsonpublic/translations/fil/default.jsonpublic/translations/fr/default.jsonpublic/translations/he/default.jsonpublic/translations/hi/default.jsonpublic/translations/hu/default.jsonpublic/translations/id/default.jsonpublic/translations/it/default.jsonpublic/translations/ja/default.jsonpublic/translations/ko/default.jsonpublic/translations/mr/default.jsonpublic/translations/nl/default.jsonpublic/translations/no/default.jsonpublic/translations/pl/default.jsonpublic/translations/pt/default.jsonpublic/translations/ro/default.jsonpublic/translations/ru/default.jsonpublic/translations/sq/default.jsonpublic/translations/sv/default.jsonpublic/translations/te/default.jsonpublic/translations/th/default.jsonpublic/translations/tr/default.jsonpublic/translations/uk/default.jsonpublic/translations/ur/default.jsonpublic/translations/vi/default.jsonpublic/translations/zh/default.jsonsrc/__tests__/app.test.tsxsrc/app.tsxsrc/components/board-buttons/__tests__/board-buttons.test.tsxsrc/components/board-buttons/board-buttons.module.csssrc/components/board-buttons/board-buttons.tsxsrc/components/catalog-row/__tests__/catalog-row.test.tsxsrc/components/catalog-row/catalog-row.tsxsrc/components/comment-content/__tests__/comment-content.test.tsxsrc/components/comment-content/comment-content.tsxsrc/components/comment-media/__tests__/comment-media.test.tsxsrc/components/comment-media/comment-media.module.csssrc/components/comment-media/comment-media.tsxsrc/components/comment-media/index.tssrc/components/post-desktop/post-menu-desktop/post-menu-desktop.tsxsrc/components/post-mobile/post-menu-mobile/post-menu-mobile.tsxsrc/components/post-mobile/post-mobile.tsxsrc/hooks/__tests__/use-hidden-catalog-threads.test.tsxsrc/hooks/__tests__/use-hide.test.tsxsrc/hooks/__tests__/use-prune-hidden-catalog-threads.test.tsxsrc/hooks/use-hidden-catalog-threads.tssrc/hooks/use-hide.tssrc/hooks/use-prune-hidden-catalog-threads.tssrc/lib/utils/post-menu-props.tssrc/stores/use-hidden-catalog-threads-store.tssrc/views/catalog/__tests__/catalog.test.tsxsrc/views/catalog/catalog.tsx
| pkcWebSocketServer.on('error', (e) => logger.log('pkc rpc error', e)); | ||
|
|
||
| logger.log(`pkc rpc: listening on ws://localhost:${port} (local connections only)`); | ||
| logger.log(`pkc rpc: listening on ws://localhost:${port}/${authKey} (secret auth key for remote connections)`); |
There was a problem hiding this comment.
Do not log the PKC RPC auth key in plaintext.
Line 6 includes the full authKey in logs, which leaks a live secret and weakens access control if logs are exposed.
🔒 Proposed fix
- logger.log(`pkc rpc: listening on ws://localhost:${port}/${authKey} (secret auth key for remote connections)`);
+ logger.log(`pkc rpc: listening on ws://localhost:${port}/[redacted] (secret auth key for remote connections)`);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| logger.log(`pkc rpc: listening on ws://localhost:${port}/${authKey} (secret auth key for remote connections)`); | |
| logger.log(`pkc rpc: listening on ws://localhost:${port}/[redacted] (secret auth key for remote connections)`); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@electron/start-pkc-rpc-core.js` at line 6, The log currently prints the full
secret authKey (logger.log in start-pkc-rpc-core.js), which leaks credentials;
change the logging in the PKC RPC startup to avoid printing authKey in
plaintext—log only non-sensitive info such as the port and a redacted or hashed
representation of authKey (e.g., mask all but last 4 chars or log
"authKey=REDACTED"/its SHA256) while keeping the existing message context so
callers can still see the service is listening without exposing the secret.
| "p2p_stats": "P2P stats", | ||
| "enable_pure_p2p": "Enable pure P2P", | ||
| "enable_pure_p2p_tip": "Use a browser P2P node instead of centralized IPFS gateways.", | ||
| "enable_pure_p2p_tip": "Use a browser P2P node instead of gateway compatibility mode.", |
There was a problem hiding this comment.
Localize this tooltip in Arabic
Line 377 is still English (Use a browser P2P node instead of gateway compatibility mode.), so Arabic users will see mixed-language UI.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/translations/ar/default.json` at line 377, Replace the English string
for the translation key "enable_pure_p2p_tip" with an Arabic translation so the
UI is fully localized for Arabic users; update the value of
"enable_pure_p2p_tip" from "Use a browser P2P node instead of gateway
compatibility mode." to an appropriate Arabic text (for example: "استخدم عقدة
P2P في المتصفح بدلاً من وضع التوافق مع البوابة.") ensuring the JSON string
remains valid.
| "pass_question_governance": "Will 5chan Pass control directory governance?", | ||
| "pass_answer_governance": "No. The current direction is that 5chan Pass holders can participate in directory voting, but final governance mechanics should include BSO-holder alignment and safeguards instead of pass-only final control.", | ||
| "app_is_up_to_date": "you're on latest" | ||
| "app_is_up_to_date": "you're on latest", |
There was a problem hiding this comment.
Tighten English copy for update status
Line 383 (you're on latest) reads ungrammatical; consider “You're on the latest version.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/translations/en/default.json` at line 383, Replace the ungrammatical
value for the "app_is_up_to_date" key: change the string "you're on latest" to a
properly capitalized and punctuated phrase such as "You're on the latest
version." in the JSON so the translation reads correctly.
| "p2p_stats": "P2P stats", | ||
| "enable_pure_p2p": "Enable pure P2P", | ||
| "enable_pure_p2p_tip": "Use a browser P2P node instead of centralized IPFS gateways.", | ||
| "enable_pure_p2p_tip": "Use a browser P2P node instead of gateway compatibility mode.", |
There was a problem hiding this comment.
Localize enable_pure_p2p_tip for this locale.
Line 377 is still English, which causes mixed-language UI in Romanian. Please translate this key here and in the same changed key across public/translations/ru/default.json, public/translations/sq/default.json, public/translations/sv/default.json, public/translations/te/default.json, public/translations/th/default.json, public/translations/tr/default.json, and public/translations/uk/default.json.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/translations/ro/default.json` at line 377, The translation key
"enable_pure_p2p_tip" is still in English in the Romanian locale; replace the
English value with an appropriate Romanian translation for that key in
public/translations/ro/default.json and apply the corresponding translated text
to the same key ("enable_pure_p2p_tip") in the other locales mentioned (ru, sq,
sv, te, th, tr, uk) so the UI is consistent across languages; ensure you only
change the value string (keep the JSON key intact and maintain valid JSON
quoting/commas).
| "p2p_stats": "P2P stats", | ||
| "enable_pure_p2p": "Enable pure P2P", | ||
| "enable_pure_p2p_tip": "Use a browser P2P node instead of centralized IPFS gateways.", | ||
| "enable_pure_p2p_tip": "Use a browser P2P node instead of gateway compatibility mode.", |
There was a problem hiding this comment.
Localize enable_pure_p2p_tip for Urdu UI.
Line 377 is still English, which causes mixed-language UX in the Urdu locale. Please provide an Urdu translation for this key.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/translations/ur/default.json` at line 377, The key
"enable_pure_p2p_tip" currently has an English string; replace its value with an
Urdu translation so the Urdu locale is consistent — update the JSON value for
"enable_pure_p2p_tip" to an appropriate Urdu phrase such as "گیٹ وے کمپٹیبیلٹی
موڈ کی بجائے براؤزر P2P نوڈ استعمال کریں۔" ensuring proper JSON quoting and
punctuation for the existing "enable_pure_p2p_tip" entry.
| "p2p_stats": "P2P stats", | ||
| "enable_pure_p2p": "Enable pure P2P", | ||
| "enable_pure_p2p_tip": "Use a browser P2P node instead of centralized IPFS gateways.", | ||
| "enable_pure_p2p_tip": "Use a browser P2P node instead of gateway compatibility mode.", |
There was a problem hiding this comment.
Translate enable_pure_p2p_tip into Vietnamese.
Line 377 is still in English, so this locale will show mixed-language copy in settings.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/translations/vi/default.json` at line 377, Replace the English value
for the translation key "enable_pure_p2p_tip" with a Vietnamese translation so
the locale isn't mixed; update the value for "enable_pure_p2p_tip" (in
public/translations/vi/default.json) to a Vietnamese string such as "Sử dụng nút
P2P trên trình duyệt thay vì chế độ tương thích gateway." to match the rest of
the locale.
| "p2p_stats": "P2P stats", | ||
| "enable_pure_p2p": "Enable pure P2P", | ||
| "enable_pure_p2p_tip": "Use a browser P2P node instead of centralized IPFS gateways.", | ||
| "enable_pure_p2p_tip": "Use a browser P2P node instead of gateway compatibility mode.", |
There was a problem hiding this comment.
Localize enable_pure_p2p_tip for Chinese users.
Line 377 is English while the rest of this section is localized, which introduces inconsistent UI language.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/translations/zh/default.json` at line 377, The translation for the key
"enable_pure_p2p_tip" is still in English; replace the English value with a
Chinese localized string while keeping the key intact (e.g., "使用浏览器 P2P
节点,而不是网关兼容模式。") so the UI language is consistent in this section.
| .mediaLoadFailureStatus { | ||
| position: absolute; | ||
| width: 1px; | ||
| height: 1px; | ||
| padding: 0; | ||
| margin: -1px; | ||
| overflow: hidden; | ||
| clip: rect(0 0 0 0); | ||
| white-space: nowrap; | ||
| border: 0; | ||
| } |
There was a problem hiding this comment.
Replace deprecated clip property with modern clip-path.
The clip property at line 201 is deprecated. Modern CSS uses clip-path instead for clipping regions.
♻️ Proposed fix
.mediaLoadFailureStatus {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
- clip: rect(0 0 0 0);
+ clip-path: inset(50%);
white-space: nowrap;
border: 0;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .mediaLoadFailureStatus { | |
| position: absolute; | |
| width: 1px; | |
| height: 1px; | |
| padding: 0; | |
| margin: -1px; | |
| overflow: hidden; | |
| clip: rect(0 0 0 0); | |
| white-space: nowrap; | |
| border: 0; | |
| } | |
| .mediaLoadFailureStatus { | |
| position: absolute; | |
| width: 1px; | |
| height: 1px; | |
| padding: 0; | |
| margin: -1px; | |
| overflow: hidden; | |
| clip-path: inset(50%); | |
| white-space: nowrap; | |
| border: 0; | |
| } |
🧰 Tools
🪛 Stylelint (17.11.0)
[error] 201-201: Deprecated property "clip" (property-no-deprecated)
(property-no-deprecated)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/comment-media/comment-media.module.css` around lines 194 -
204, The .mediaLoadFailureStatus rule uses the deprecated clip property; replace
the clip: rect(0 0 0 0); declaration in the .mediaLoadFailureStatus CSS rule
with a modern clip-path equivalent (e.g., clip-path: inset(0 0 0 0);) and
include a vendor-prefixed fallback (-webkit-clip-path) to preserve behavior
across browsers while keeping the rest of the visually-hidden declarations
intact.
| const handleLoad = () => { | ||
| setFailedUrl((currentFailedUrl) => (currentFailedUrl === url ? undefined : currentFailedUrl)); | ||
| onMediaLoadFailureChange?.(undefined); | ||
| }; |
There was a problem hiding this comment.
State synchronization bug in handleLoad.
The callback onMediaLoadFailureChange?.(undefined) is called unconditionally, but failedUrl is only cleared when currentFailedUrl === url. When the media URL changes while a previous URL is still marked as failed, the component state and parent state desynchronize.
Scenario:
- Image B fails →
failedUrl = "B", parent receives"B"✓ - URL changes to C, C loads successfully
setFailedUrlseescurrentFailedUrl = "B"andurl = "C"→"B" !== "C"→ keepsfailedUrl = "B"- Callback fires with
undefined→ parent clears its state - Result: component has
failedUrl = "B", parent hasundefined→ inconsistent state
🐛 Proposed fix: only notify parent when state actually changes
const handleLoad = () => {
- setFailedUrl((currentFailedUrl) => (currentFailedUrl === url ? undefined : currentFailedUrl));
- onMediaLoadFailureChange?.(undefined);
+ setFailedUrl((currentFailedUrl) => {
+ if (currentFailedUrl === url) {
+ onMediaLoadFailureChange?.(undefined);
+ return undefined;
+ }
+ return currentFailedUrl;
+ });
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleLoad = () => { | |
| setFailedUrl((currentFailedUrl) => (currentFailedUrl === url ? undefined : currentFailedUrl)); | |
| onMediaLoadFailureChange?.(undefined); | |
| }; | |
| const handleLoad = () => { | |
| setFailedUrl((currentFailedUrl) => { | |
| if (currentFailedUrl === url) { | |
| onMediaLoadFailureChange?.(undefined); | |
| return undefined; | |
| } | |
| return currentFailedUrl; | |
| }); | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/comment-media/comment-media.tsx` around lines 388 - 391,
handleLoad currently always calls onMediaLoadFailureChange(undefined) even when
failedUrl isn't cleared, causing parent/component desync; update handleLoad so
it uses the functional updater result to determine if failedUrl was actually
changed (i.e., check whether currentFailedUrl === url) and only invoke
onMediaLoadFailureChange(undefined) when you actually clear the failedUrl;
reference the handleLoad function, setFailedUrl updater, failedUrl state,
onMediaLoadFailureChange callback, and url prop when implementing this
conditional notification.
| useEffect(() => { | ||
| if (!enabled || removedHiddenThreadCids.length === 0) { | ||
| return; | ||
| } | ||
|
|
||
| for (const cid of removedHiddenThreadCids) { | ||
| if (pendingPruneCidsRef.current.has(cid)) { | ||
| continue; | ||
| } | ||
|
|
||
| pendingPruneCidsRef.current.add(cid); | ||
| void accountsStore | ||
| .getState() | ||
| .accountsActions.unblockCid(cid) | ||
| .catch((error: unknown) => { | ||
| const { accounts, activeAccountId } = accountsStore.getState(); | ||
| if (activeAccountId && accounts?.[activeAccountId]?.blockedCids?.[cid]) { | ||
| console.error('Failed to remove stale hidden thread from account', error); | ||
| } | ||
| }) | ||
| .finally(() => { | ||
| pendingPruneCidsRef.current.delete(cid); | ||
| }); | ||
| } | ||
| }, [enabled, removedHiddenThreadCids]); |
There was a problem hiding this comment.
Persistent unblockCid failures can be retried in a tight loop.
pendingPruneCidsRef only prevents concurrent duplicate calls — once .finally (Line 145-147) removes the CID, the effect is free to re-issue unblockCid for the same CID on the very next render. The effect's deps include removedHiddenThreadCids, which is derived from rawBoardCatalogState (and thus communitiesPages, which updates frequently). If accountsActions.unblockCid(cid) rejects persistently (server error, transient outage), the failed CID stays in blockedCids, stays archived-not + in-scope + not-in-rootThreadCids, and gets re-attempted on every catalog page update — silently, since the error is only logged when accounts[activeAccountId].blockedCids[cid] is truthy (which it always is on persistent failure).
Consider one of:
- Track per-CID failure counts in a ref and skip after N consecutive failures (with a reset on
communityAddress/accountIdchange). - Move pending-set removal out of
.finallyfor the error path and require an explicit retry trigger. - Add a short cooldown before the same CID is retried.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/hooks/use-prune-hidden-catalog-threads.ts` around lines 125 - 149, The
effect currently requeues failed unblockCid calls immediately because
pendingPruneCidsRef.current.delete(cid) runs in .finally, causing tight retry
loops when accountsActions.unblockCid(cid) keeps rejecting; change the logic in
useEffect to track per-CID failure attempts (e.g., failureCountsRef keyed by
cid) and only remove cid from pendingPruneCidsRef on success or after a short
cooldown/after N failures, incrementing failureCountsRef[cid] on rejection and
skipping retries when count >= N; also reset failureCountsRef when
activeAccountId or communityAddress (or whatever drives removedHiddenThreadCids)
changes so retries resume for new contexts. Ensure you update the handlers
around accountsActions.unblockCid, the .catch and .finally behavior, and the
dependency list (useEffect) references for resets.
|
Review triage before merge: the branch is already up to date with current master, and its unique diff is only electron/main.js, electron/user-agent.js, and electron/user-agent.test.js. Cursor Bugbot's PKC RPC options comment is declined: the installed @pkcprotocol/pkc-js/rpc schema and implementation expect pkcOptions. CodeRabbit reviewed the older 486ef39..6c68732 range and posted comments on files already present on master, so those are pre-existing/follow-up items and do not block this Electron user-agent fix. Verified locally with yarn build, yarn lint, yarn type-check, and yarn test; GitHub checks are green. |


Summary
Root Cause
The Linux desktop app in 0.8.4 advertised
Chrome/116.0.0.0even though the bundled Electron runtime is much newer. The live spam-blocker server showed affected attempts loading the iframe and later calling/challenge/verifywithout any/challenge/completerequest, which means the Turnstile widget did not produce a successful completion callback before the user submitted.Validation
corepack yarn vitest run electron/user-agent.test.jscorepack yarn test --runcorepack yarn lintcorepack yarn type-checkcorepack yarn knipcorepack yarn buildNote
Medium Risk
Medium risk because it changes Electron request headers (user agent) and modifies feed/catalog rendering (hidden threads toggle, hidden post masking, and media error UI), which could affect navigation and content display across platforms.
Overview
Electron desktop user agent is now generated from the bundled Chromium major version via new
electron/user-agent.js, replacing the previously hardcoded Chrome 116 UA string inelectron/main.js(with tests to cover platform variants and fallback behavior).Refactors PKC RPC startup by extracting server creation/logging into
start-pkc-rpc-core.jsand wiringstart-pkc-rpc.jsthrough it, adding a focused unit test.Improves catalog/feed UX around hidden and failed media: adds a hidden-thread count +
Show/Backtoggle in board buttons (with supportinguseHiddenCatalogThreadscoverage), allows catalog rows to optionally render hidden post content, and replaces the “file deleted” fallback with a localized media-load failure warning + “open original” link;CommentContentgainsappendContentto surface this guidance under posts on mobile. Also adjusts board blotter rendering rules and updates translations for the new strings and a few existing labels.Reviewed by Cursor Bugbot for commit 6c68732. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Localization