-
Notifications
You must be signed in to change notification settings - Fork 6
fix: pathological pdf parts selection #297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
✅ Deploy Preview for graypaper-reader ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
WalkthroughAdds size-based filtering for “pathological” Synctex blocks in lookup and range APIs, a dev-only debug rectangle drawer, minor Synctex-load refactor, a devtools feature-flag system (including DEBUG_DRAW_BLOCKS), and disables pointer events on PDF text-layer end-of-content/br elements. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Pdf as PdfViewer
participant CodeSync as CodeSyncProvider
participant Synctex as SynctexData
participant Debug as debugDrawBlock
participant IsPath as isPathologicalBlock
User->>Pdf: click / request location/range
Pdf->>CodeSync: getSynctexBlockAtLocation(point)
CodeSync->>Synctex: fetch candidate blocks
loop per candidate
CodeSync->>IsPath: isPathologicalBlock(block)
IsPath-->>CodeSync: true/false
alt non-pathological
CodeSync->>Debug: debugDrawBlock(page, rect) [if DEBUG_DRAW_BLOCKS]
Debug-->>CodeSync: (draw + auto-undo)
CodeSync-->>Pdf: return matching block
else pathological
Note right of CodeSync: skip block
end
end
Pdf->>CodeSync: getSynctexBlockRange(range)
CodeSync->>Synctex: compute raw rangeOfBlocks
CodeSync->>IsPath: filter pathological -> goodBlocks
CodeSync->>Debug: draw each goodBlock (if feature enabled)
CodeSync-->>Pdf: return goodBlocks
Note over CodeSync,Synctex: On synctex load: assign to local var, perform transient pass, then set synctex state
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
src/components/CodeSyncProvider/CodeSyncProvider.tsx (4)
31-34: Tighten type and clarify comment; consider extracting thresholds.Use the project’s
ISynctexBlocktype (or aPick) and fix the comment grammar. Optionally extract the “magic numbers” to named constants for easier tuning and A/B experiments.Apply within this hunk:
-const isPathologicalBlock = (block: { width: number; height: number }) => { - /* checking simple for dimension is an naive attempt to find too big blocks; there is a risk that it matches healthy big blocks */ +const isPathologicalBlock = (block: Pick<ISynctexBlock, "width" | "height">) => { + /* Simple dimension check to catch oversized blocks; may misclassify legitimately large blocks. */ return (block.height > 0.1 && block.width > 0.1) || block.height > 0.5 || block.width > 0.5; };Optionally (outside this hunk), define:
const PATHOLOGICAL_MAX_DIM = 0.5; const PATHOLOGICAL_BOTH_DIM = 0.1;and use them in the predicate.
65-67: Skipping pathological blocks: confirm behavior when all candidates are filtered.If every block on a page (rare but possible) is flagged pathological,
lastMatchremains null. Verify downstream UX (e.g., cursor feedback) degrades gracefully; consider a fallback to nearest non-pathological neighbor or relaxing thresholds in that edge case.
112-116: Range filtering may return an empty selection; consider a small fallback.Filtering after slice can drop both ends and yield an empty array, which can look like a lost selection. Optionally add a bounded expansion to find nearby non-pathological blocks.
Apply within this hunk:
- synctexData.blocksByPage - .get(startBlockId.pageNumber) - ?.slice(startBlockId.index, endBlockId.index + 1) - .filter((block) => !isPathologicalBlock(block)) || [] + (() => { + const pageBlocks = synctexData.blocksByPage.get(startBlockId.pageNumber) ?? []; + const slice = pageBlocks.slice(startBlockId.index, endBlockId.index + 1); + const filtered = slice.filter((b) => !isPathologicalBlock(b)); + if (filtered.length) return filtered; + const MAX_EXPAND = 2; + for (let d = 1; d <= MAX_EXPAND; d++) { + const left = Math.max(startBlockId.index - d, 0); + const right = Math.min(endBlockId.index + d, pageBlocks.length - 1); + const attempt = pageBlocks.slice(left, right + 1).filter((b) => !isPathologicalBlock(b)); + if (attempt.length) return attempt; + } + return []; + })()
55-55: Remove or gate stray debug logsAvoid noisy logs in hot paths. Found the following occurrences:
- src/components/CodeSyncProvider/CodeSyncProvider.tsx (line 55)
console.log("start getSynctexBlockAtLocation");
// console.debug("getSynctexBlockAtLocation"); // optionally gate behind DEV
- src/components/PdfViewer/SelectionRenderer/SelectionRenderer.tsx (line 29)
- console.debug("scroll: checking if we need to scroll");
- // console.debug("scroll: checking if we need to scroll"); // optionally gate behind DEV
To ensure no other stray logs remain, you can re-run: ```shell rg -nP '\bconsole\.(log|debug)\(' -g '*.ts' -g '*.tsx' -C2 src/
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/components/CodeSyncProvider/CodeSyncProvider.tsx(4 hunks)src/components/PdfViewer/PdfViewer.css(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.css
⚙️ CodeRabbit configuration file
Review CSS code with focus on Tailwind CSS 4.x best practices and conventions. Ensure compatibility with Tailwind 4.x features and syntax.
Files:
src/components/PdfViewer/PdfViewer.css
**/*.{js,jsx,ts,tsx}
⚙️ CodeRabbit configuration file
When reviewing Tailwind CSS classes, ensure they follow Tailwind 4.x conventions and suggest modern 4.x alternatives for deprecated patterns.
Files:
src/components/CodeSyncProvider/CodeSyncProvider.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Redirect rules - graypaper-reader
- GitHub Check: Header rules - graypaper-reader
- GitHub Check: Pages changed - graypaper-reader
🔇 Additional comments (1)
src/components/PdfViewer/PdfViewer.css (1)
52-54: Good fix: prevent non-text nodes from intercepting clicks in the text layer.Disabling pointer events on
.endOfContentandbrwill reduce spurious hit-testing during selection. Looks safe given PDF.js’ structure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Nitpick comments (4)
src/components/CodeSyncProvider/debugDrawBlock.ts (2)
6-7: Add proper return type annotation for early returnsThe function returns
void | (() => void), but early returns don't explicitly returnundefined. While JavaScript handles this, it's better to be explicit for consistency with the undo function return.- if (!canvas) return; + if (!canvas) return undefined; const context = canvas.getContext("2d"); - if (!context) return; + if (!context) return undefined;Also applies to: 10-11
51-59: Consider making the timeout duration configurableThe 2500ms timeout is hardcoded. Consider making this configurable for different debugging scenarios.
-export function debugDrawBlock(page: number, rectParams: { width: number; height: number; top: number; left: number }) { +export function debugDrawBlock(page: number, rectParams: { width: number; height: number; top: number; left: number }, duration = 2500) { const removeCallback = drawRectangle(page, rectParams); if (!removeCallback) return; setTimeout(() => { removeCallback(); - }, 2500); + }, duration); }src/components/CodeSyncProvider/CodeSyncProvider.tsx (2)
137-138: Remove unnecessary || [] fallbacksThe
filtermethod always returns an array, so the|| []fallback is redundant.- const badBlocks = rangeOfBlocks.filter((block) => isPathologicalBlock(block)) || []; - const goodBlocks = rangeOfBlocks.filter((block) => !isPathologicalBlock(block)) || []; + const badBlocks = rangeOfBlocks.filter((block) => isPathologicalBlock(block)); + const goodBlocks = rangeOfBlocks.filter((block) => !isPathologicalBlock(block));
137-144: Consider performance optimization for block filteringYou're iterating through
rangeOfBlockstwice to separate good and bad blocks. Consider using a single pass withreduceor a simple loop.- const badBlocks = rangeOfBlocks.filter((block) => isPathologicalBlock(block)); - const goodBlocks = rangeOfBlocks.filter((block) => !isPathologicalBlock(block)); + const goodBlocks: ISynctexBlock[] = []; + const badBlocks: ISynctexBlock[] = []; + for (const block of rangeOfBlocks) { + if (isPathologicalBlock(block)) { + badBlocks.push(block); + } else { + goodBlocks.push(block); + } + }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/components/CodeSyncProvider/CodeSyncProvider.tsx(6 hunks)src/components/CodeSyncProvider/debugDrawBlock.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}
⚙️ CodeRabbit configuration file
When reviewing Tailwind CSS classes, ensure they follow Tailwind 4.x conventions and suggest modern 4.x alternatives for deprecated patterns.
Files:
src/components/CodeSyncProvider/debugDrawBlock.tssrc/components/CodeSyncProvider/CodeSyncProvider.tsx
🧬 Code graph analysis (1)
src/components/CodeSyncProvider/CodeSyncProvider.tsx (1)
src/components/CodeSyncProvider/debugDrawBlock.ts (1)
debugDrawBlock(51-59)
🪛 GitHub Actions: Node.js CI
src/components/CodeSyncProvider/CodeSyncProvider.tsx
[error] 48-48: Biome linter: 'forEach' usage detected. Prefer 'for...of' instead of 'forEach'. (lint/complexity/noForEach)
[error] 49-49: Biome linter: 'forEach' usage detected. Prefer 'for...of' instead of 'forEach'. (lint/complexity/noForEach)
[error] 50-50: Biome linter: 'forEach' usage detected. Prefer 'for...of' instead of 'forEach'. (lint/complexity/noForEach)
[error] 59-59: Biome linter: 'forEach' usage detected. Prefer 'for...of' instead of 'forEach'. (lint/complexity/noForEach)
[error] 60-60: Biome linter: 'forEach' usage detected. Prefer 'for...of' instead of 'forEach'. (lint/complexity/noForEach)
[error] 142-142: Biome linter: 'forEach' usage detected. Prefer 'for...of' instead of 'forEach'. (lint/complexity/noForEach)
🔇 Additional comments (2)
src/components/CodeSyncProvider/debugDrawBlock.ts (1)
37-37: Good catch on the rectangle drawing fix!The comment indicates this was a bug fix where the bottom line calculation was corrected from subtraction to addition. This ensures the rectangle is drawn correctly.
src/components/CodeSyncProvider/CodeSyncProvider.tsx (1)
88-90: Good implementation of pathological block filteringThe filtering logic is correctly applied to skip pathological blocks during location matching, which should prevent selection issues with oversized blocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
src/components/CodeSyncProvider/debugDrawBlock.ts (2)
1-1: Provide cleanup API for cached ImageData to avoid leaksThe per-page Map grows unbounded and never clears. Add an explicit cleanup.
Apply:
+export function clearDebugState(page?: number) { + if (page !== undefined) { + statePerPage.delete(page); + } else { + statePerPage.clear(); + } +}
42-47: Delete cached snapshot after undo to free memoryAfter restoration, the entry can be removed safely.
return function undo() { const savedState = statePerPage.get(page); if (savedState) { context.putImageData(savedState, 0, 0); + statePerPage.delete(page); } };
🧹 Nitpick comments (14)
src/components/CodeSyncProvider/debugDrawBlock.ts (3)
12-15: Handle canvas resize: refresh snapshot if dimensions changedIf the canvas is re-rendered/zoomed, the cached ImageData may no longer match.
- if (!statePerPage.has(page)) { - const savedState = context.getImageData(0, 0, canvas.width, canvas.height); - statePerPage.set(page, savedState); - } + const prev = statePerPage.get(page); + if (!prev || prev.width !== canvas.width || prev.height !== canvas.height) { + const savedState = context.getImageData(0, 0, canvas.width, canvas.height); + statePerPage.set(page, savedState); + }
24-27: Clamp normalized rect to [0,1] to avoid out-of-bounds drawingProtect against pathological inputs or rounding.
- const x1 = left * canvasWidth; - const y1 = top * canvasHeight; - const rectWidth = width * canvasWidth; - const rectHeight = height * canvasHeight; + const clamp = (v: number, min = 0, max = 1) => Math.min(max, Math.max(min, v)); + const x1 = clamp(left) * canvasWidth; + const y1 = clamp(top) * canvasHeight; + const rectWidth = clamp(width) * canvasWidth; + const rectHeight = clamp(height) * canvasHeight;
4-4: Prefer typed querySelector for better type-safetyAvoid the cast; let TS infer the element type.
- const canvas = document.querySelector(`[data-page-number="${page}"] canvas`) as HTMLCanvasElement; + const canvas = document.querySelector<HTMLCanvasElement>(`[data-page-number="${page}"] canvas`);src/devtools/featureFlags.ts (3)
22-24: Make isDevelopment() SSR-safe and DRY with central helperDirect window access can break in SSR/tests; also duplicated in devtools/index.ts.
- private isDevelopment(): boolean { - return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"; - } + private isDevelopment(): boolean { + return typeof window !== "undefined" && + (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"); + }Follow-up: consider extracting to a single shared util (e.g., devtools/env.ts) and reusing here and in index.ts.
30-43: Defensive access to sessionStorageSome environments (e.g., privacy modes) throw on sessionStorage access.
- isEnabled(flagKey: FeatureFlagKey): boolean { + isEnabled(flagKey: FeatureFlagKey): boolean { if (!this.isDevelopment()) { return false; } const flag = FEATURE_FLAGS[flagKey]; - const storedValue = sessionStorage.getItem(this.getStorageKey(flag.key)); + let storedValue: string | null = null; + try { + storedValue = typeof sessionStorage !== "undefined" + ? sessionStorage.getItem(this.getStorageKey(flag.key)) + : null; + } catch { + // ignore and fall back to default + }
45-61: Emit a CustomEvent on flag changes; let hooks subscribe instead of pollingImproves responsiveness and removes reliance on sessionStorage 'storage' events.
+ private emitChange(flagKey: FeatureFlagKey, value?: boolean): void { + try { + window.dispatchEvent( + new CustomEvent("gp:featureFlagChanged", { detail: { flagKey, value } }), + ); + } catch { + // no-op + } + }Then after each set/remove in:
- toggle(): call
this.emitChange(flagKey, newValue)- enable():
this.emitChange(flagKey, true)- disable():
this.emitChange(flagKey, false)- reset():
this.emitChange(flagKey)- resetAll(): after loop,
this.emitChange("DEBUG_DRAW_BLOCKS" as FeatureFlagKey)or emit per flag in loop.Also applies to: 62-72, 73-83, 84-94, 95-107
src/devtools/useFeatureFlag.tsx (2)
7-29: Stabilize effect and subscribe to explicit flag-change eventAvoid restarting interval on each state flip; listen to gp:featureFlagChanged.
- useEffect(() => { + useEffect(() => { const handleStorageChange = (e: StorageEvent) => { if (e.storageArea === sessionStorage && e.key?.startsWith("gp_feature_flag_")) { - setEnabled(isFeatureEnabled(flagKey)); + setEnabled(isFeatureEnabled(flagKey)); } }; const checkFlagState = () => { - const currentState = isFeatureEnabled(flagKey); - if (currentState !== enabled) { - setEnabled(currentState); - } + setEnabled(isFeatureEnabled(flagKey)); }; const intervalId = setInterval(checkFlagState, 1000); + const handleCustomEvent = () => setEnabled(isFeatureEnabled(flagKey)); + window.addEventListener("gp:featureFlagChanged", handleCustomEvent as EventListener); window.addEventListener("storage", handleStorageChange); return () => { clearInterval(intervalId); + window.removeEventListener("gp:featureFlagChanged", handleCustomEvent as EventListener); window.removeEventListener("storage", handleStorageChange); }; - }, [flagKey, enabled]); + }, [flagKey]);Note: sessionStorage typically does not fire storage events within the same tab; the custom event closes that gap.
34-47: Preserve component name in HOC for better DevTools DXexport function withFeatureFlag<P extends object>( flagKey: FeatureFlagKey, Component: React.ComponentType<P>, ): React.ComponentType<P> { - return function FeatureFlagWrapper(props: P) { + function FeatureFlagWrapper(props: P) { const isEnabled = useFeatureFlag(flagKey); if (!isEnabled) { return null; } return <Component {...props} />; - }; + } + FeatureFlagWrapper.displayName = `WithFeatureFlag(${Component.displayName || Component.name || "Component"})`; + return FeatureFlagWrapper; }src/main.tsx (1)
7-7: Avoid bundling devtools in production builds via dynamic importTree-shakes dev-only code; keeps gpDev out of prod bundles.
-import { initDevTools } from "./devtools/initDevTools"; +// Load devtools only in development +if (import.meta.env.DEV) { + import("./devtools/initDevTools").then((m) => m.initDevTools()); +} @@ -initDevTools();Also applies to: 17-18
src/devtools/index.ts (1)
8-10: SSR-safe isDevelopment() and eliminate duplication with featureFlagsGuard window access and consider centralizing this helper.
export function isDevelopment(): boolean { - return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"; + return typeof window !== "undefined" && + (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"); }Follow-up: extract to devtools/env.ts and reuse here and in FeatureFlagsManager.
src/devtools/initDevTools.ts (4)
1-1: Import and use central isDevelopment to enable tree-shaking and consistent gatingLeverage the shared devtools helper so bundlers can drop this code in production.
-import { type FeatureFlagKey, featureFlags } from "./featureFlags"; +import { type FeatureFlagKey, featureFlags } from "./featureFlags"; +import { isDevelopment } from ".";
3-15: Avoid type/runtime mismatch: make gpDev optional (or always stub it) and factor a reusable API typeRight now the type claims gpDev always exists, but the function early-returns without assigning it off-localhost. Make it optional (or always define a stub); factoring a type also tightens reuse.
declare global { interface Window { - gpDev: { - listFlags: () => void; - toggle: (flagKey: FeatureFlagKey) => boolean; - enable: (flagKey: FeatureFlagKey) => void; - disable: (flagKey: FeatureFlagKey) => void; - reset: (flagKey: FeatureFlagKey) => void; - resetAll: () => void; - help: () => void; - }; + gpDev?: GpDevAPI; } }Add this type near the imports:
type GpDevAPI = Readonly<{ listFlags: () => void; toggle: (flagKey: FeatureFlagKey) => boolean; enable: (flagKey: FeatureFlagKey) => void; disable: (flagKey: FeatureFlagKey) => void; reset: (flagKey: FeatureFlagKey) => void; resetAll: () => void; help: () => void; }>;
17-21: Strengthen environment/host gating and SSR-safety; add IPv6/0.0.0.0; allow HMR idempotencyPrevents server-side crashes, avoids accidental exposure on custom dev hosts, and dedupes re-inits.
export function initDevTools(): void { - if (window.location.hostname !== "localhost" && window.location.hostname !== "127.0.0.1") { - return; - } + if (typeof window === "undefined") return; // SSR-safety + if (!isDevelopment) return; // build-time tree-shake + const host = window.location.hostname; + const isLocalhost = + host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0"; + if (!isLocalhost) return; + if (window.gpDev) return; // idempotent (HMR)
22-30: Harden gpDev: freeze API and expose as non-enumerable, read-only propertyPrevents accidental mutation in console sessions and hides it from object iterations while keeping DX.
- window.gpDev = { - listFlags: () => featureFlags.listFlags(), - toggle: (flagKey: FeatureFlagKey) => featureFlags.toggle(flagKey), - enable: (flagKey: FeatureFlagKey) => featureFlags.enable(flagKey), - disable: (flagKey: FeatureFlagKey) => featureFlags.disable(flagKey), - reset: (flagKey: FeatureFlagKey) => featureFlags.reset(flagKey), - resetAll: () => featureFlags.resetAll(), - help: () => featureFlags.help(), - }; + const api: GpDevAPI = Object.freeze({ + listFlags: () => featureFlags.listFlags(), + toggle: (flagKey: FeatureFlagKey) => featureFlags.toggle(flagKey), + enable: (flagKey: FeatureFlagKey) => featureFlags.enable(flagKey), + disable: (flagKey: FeatureFlagKey) => featureFlags.disable(flagKey), + reset: (flagKey: FeatureFlagKey) => featureFlags.reset(flagKey), + resetAll: () => featureFlags.resetAll(), + help: () => featureFlags.help(), + }); + Object.defineProperty(window, "gpDev", { + value: api, + writable: false, + configurable: false, + enumerable: false, + });
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (7)
src/components/CodeSyncProvider/CodeSyncProvider.tsx(5 hunks)src/components/CodeSyncProvider/debugDrawBlock.ts(1 hunks)src/devtools/featureFlags.ts(1 hunks)src/devtools/index.ts(1 hunks)src/devtools/initDevTools.ts(1 hunks)src/devtools/useFeatureFlag.tsx(1 hunks)src/main.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/CodeSyncProvider/CodeSyncProvider.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}
⚙️ CodeRabbit configuration file
When reviewing Tailwind CSS classes, ensure they follow Tailwind 4.x conventions and suggest modern 4.x alternatives for deprecated patterns.
Files:
src/devtools/featureFlags.tssrc/devtools/initDevTools.tssrc/components/CodeSyncProvider/debugDrawBlock.tssrc/devtools/index.tssrc/main.tsxsrc/devtools/useFeatureFlag.tsx
🧬 Code graph analysis (4)
src/devtools/initDevTools.ts (1)
src/devtools/featureFlags.ts (2)
FeatureFlagKey(17-17)featureFlags(158-158)
src/devtools/index.ts (1)
src/devtools/featureFlags.ts (1)
isDevelopment(22-24)
src/main.tsx (1)
src/devtools/initDevTools.ts (1)
initDevTools(17-46)
src/devtools/useFeatureFlag.tsx (1)
src/devtools/featureFlags.ts (3)
FeatureFlagKey(17-17)isFeatureEnabled(160-162)isEnabled(30-43)
🔇 Additional comments (3)
src/components/CodeSyncProvider/debugDrawBlock.ts (1)
36-36: LGTM: corrected rectangle pathThe switch to addition on the bottom-right corner is correct.
src/devtools/initDevTools.ts (2)
32-46: LGTM on developer-facing console UXConcise banner and actionable hints; non-intrusive. Good.
22-25: No action needed: toggle() return type is correctI’ve verified that in
src/devtools/featureFlags.tsthetoggle(flagKey: FeatureFlagKey)method is explicitly declared to return a boolean (line 45). Therefore, the passthrough ininitDevTools.tscorrectly preserves the boolean return type and needs no change.
Summary by CodeRabbit
Bug Fixes
New Features
Developer Tools