Skip to content

fix(kanban-simple): wrap JSON.parse calls with safe fallback#568

Open
data-bot-coasys wants to merge 1 commit intodevfrom
fix/kanban-json-parse-safety
Open

fix(kanban-simple): wrap JSON.parse calls with safe fallback#568
data-bot-coasys wants to merge 1 commit intodevfrom
fix/kanban-json-parse-safety

Conversation

@data-bot-coasys
Copy link
Copy Markdown
Contributor

@data-bot-coasys data-bot-coasys commented Mar 22, 2026

Problem

When orderedTaskIds or orderedColumnIds contain literal:// URI strings (e.g. when tasks are created via MCP/agent tools), JSON.parse throws:

SyntaxError: Unexpected token 'l', "literal://"... is not valid JSON
    at JSON.parse (<anonymous>)
    at main-e94b43b1.js:2431:55742

This crashes the Kanban Simple View entirely.

Root Cause

The Kanban view calls JSON.parse(column.orderedTaskIds) in multiple places without any error handling. When an AI agent writes task IDs via MCP tools, the value may be stored as a literal URI string that cannot be parsed as JSON.

Fix

Added a safeJsonParse<T>() helper function that:

  • Catches JSON parse errors gracefully
  • Falls back to an empty array (or provided fallback value)
  • Logs a warning for debugging

Applied to all JSON.parse calls in:

  • Board.tsx (8 occurrences)
  • TaskSettings.tsx (4 occurrences)

Testing

Found during live testing with an AI agent (Data) creating tasks in a Flux neighbourhood via AD4M MCP tools.

Summary by CodeRabbit

  • Bug Fixes
    • Improved error handling for corrupted task and column ordering data, preventing application crashes when malformed data is encountered.
    • Enhanced board and task management stability by gracefully handling data parsing errors and falling back to default states instead of failing operations.

When orderedTaskIds or orderedColumnIds contain literal:// URI strings
(e.g. when tasks are created via MCP/agent tools), JSON.parse throws:
  SyntaxError: Unexpected token 'l', "literal://"... is not valid JSON

Added safeJsonParse helper that catches parse errors and falls back to
an empty array, preventing the view from crashing.

Fixes all JSON.parse calls in Board.tsx and TaskSettings.tsx.
@netlify
Copy link
Copy Markdown

netlify Bot commented Mar 22, 2026

Deploy Preview for fluxsocial-dev ready!

Name Link
🔨 Latest commit 919eb14
🔍 Latest deploy log https://app.netlify.com/projects/fluxsocial-dev/deploys/69bfeace8f584c00082f4bd0
😎 Deploy Preview https://deploy-preview-568--fluxsocial-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

This pull request adds a safeJsonParse helper function to handle JSON parsing failures defensively. The helper wraps JSON.parse, returning a specified fallback value and emitting a console warning on parse failures instead of throwing exceptions. This replacement is applied across multiple JSON parsing call sites in two Kanban view components for handling column and task ordering data.

Changes

Cohort / File(s) Summary
Defensive JSON Parsing
views/kanban-view-simple/src/components/Board/Board.tsx, views/kanban-view-simple/src/components/TaskSettings/TaskSettings.tsx
Introduced safeJsonParse<T> helper function and replaced direct JSON.parse() calls for orderedColumnIds, orderedTaskIds, and board ordering data. Failures now return empty array fallbacks with console warnings instead of throwing exceptions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • lucksus

Poem

🐰 A parsing problem solved with grace,
No exceptions thrown in this place!
SafeJsonParse guards every parse,
With fallbacks ready—a safety harness,
Now kanban boards dance without care! 🎭

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(kanban-simple): wrap JSON.parse calls with safe fallback' clearly summarizes the main change: adding defensive error handling to JSON.parse calls in the kanban-simple view component.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/kanban-json-parse-safety

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can generate a title for your PR based on the changes with custom instructions.

Set the reviews.auto_title_instructions setting to generate a title for your PR based on the changes in the PR with custom instructions.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
views/kanban-view-simple/src/components/TaskSettings/TaskSettings.tsx (1)

9-18: Extract safeJsonParse into a shared utility

This helper is duplicated in TaskSettings.tsx and Board.tsx. Centralizing it avoids behavioral drift and keeps warning/validation behavior consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@views/kanban-view-simple/src/components/TaskSettings/TaskSettings.tsx` around
lines 9 - 18, Duplicate safeJsonParse implementation exists in TaskSettings.tsx
and Board.tsx; extract it into a single shared utility module (e.g.,
utils/json.ts or similar), export the function as safeJsonParse<T>(value: string
| undefined | null, fallback: T): T, then remove the local definitions in
TaskSettings.tsx and Board.tsx and import the shared safeJsonParse where used.
Ensure the exported function preserves the same behavior (including the
console.warn message and slice call) and TypeScript generic signature so
existing callers in TaskSettings and Board compile without changes.
views/kanban-view-simple/src/components/Board/Board.tsx (1)

18-29: Prefer a shared parse helper instead of per-component copies

safeJsonParse is duplicated here and in TaskSettings.tsx. Move it to a shared utility module so fixes (like schema checks) are applied once.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@views/kanban-view-simple/src/components/Board/Board.tsx` around lines 18 -
29, safeJsonParse is duplicated in Board.tsx and TaskSettings.tsx; extract it
into a single shared utility (e.g., export function safeJsonParse<T>(...) in a
new/shared utils module) and replace the local copies by importing that exported
helper in both Board.tsx and TaskSettings.tsx, removing the duplicate
definitions; ensure the helper is exported (named export) and update any call
sites to use the imported safeJsonParse so future schema checks/fixes are
centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@views/kanban-view-simple/src/components/Board/Board.tsx`:
- Around line 165-167: The code calls columns.find(...) and immediately
dereferences column.orderedTaskIds and column.delete(), which can crash if the
find returns undefined; update the deleteColumn logic to check the result of
columns.find(...) (the variable column) and bail out early (return or throw a
controlled error) if it's undefined, then use column.orderedTaskIds only after
that guard (you can still call safeJsonParse on a fallback value if preferred)
and only invoke column.delete() inside the guarded block; apply the same
presence check to the other occurrence that references column (around the
Task.findAll / column.delete usage).
- Around line 21-29: safeJsonParse<T> currently returns parsed JSON without
validating its shape, which lets valid-but-incorrect values (objects, numbers,
etc.) reach callers that immediately call .splice/.filter/.map in the board
drag-and-drop logic; update safeJsonParse to perform runtime shape checks before
returning: for the kanban use case add an overload or optional validator
parameter (or a boolean branch) that verifies Array.isArray(parsed) and that
each item is a string (e.g., typeof item === 'string') when callers expect
string[]; if validation fails, log a warning and return the provided fallback.
Ensure callers (the reorder/drag-and-drop functions that call
.splice/.filter/.map) keep using safeJsonParse<string[]>(...) so the validated
type is enforced at runtime.

In `@views/kanban-view-simple/src/components/TaskSettings/TaskSettings.tsx`:
- Around line 73-75: The code assumes .find(...) returned objects (e.g., source,
destination, columnModel) and dereferences them immediately (e.g., using
source.orderedTaskIds and source.save(batchId)); add defensive null checks after
each .find call in TaskSettings.tsx and bail out or handle the missing object
(early return, user-facing error, or log and skip) before using properties or
calling methods; specifically guard the variables named source, destination, and
columnModel where they are used around the shown snippets so you never call
.orderedTaskIds, .save(...), or access columnModel properties on undefined.
- Around line 10-17: The safeJsonParse<T> helper should reject parsed non-array
values to avoid runtime crashes when callers expect string[]; modify
safeJsonParse to accept an optional type guard (validator: (v: unknown) => v is
T) and after JSON.parse run the validator and return fallback if it fails.
Update all callers that expect string[] (the safeJsonParse<string[]>() usages in
TaskSettings where the result is immediately spread or .filter() is called) to
pass Array.isArray as the validator (or a stricter element-type guard) so only
actual arrays are returned; keep the existing try/catch fallback behavior for
parse errors.

---

Nitpick comments:
In `@views/kanban-view-simple/src/components/Board/Board.tsx`:
- Around line 18-29: safeJsonParse is duplicated in Board.tsx and
TaskSettings.tsx; extract it into a single shared utility (e.g., export function
safeJsonParse<T>(...) in a new/shared utils module) and replace the local copies
by importing that exported helper in both Board.tsx and TaskSettings.tsx,
removing the duplicate definitions; ensure the helper is exported (named export)
and update any call sites to use the imported safeJsonParse so future schema
checks/fixes are centralized.

In `@views/kanban-view-simple/src/components/TaskSettings/TaskSettings.tsx`:
- Around line 9-18: Duplicate safeJsonParse implementation exists in
TaskSettings.tsx and Board.tsx; extract it into a single shared utility module
(e.g., utils/json.ts or similar), export the function as safeJsonParse<T>(value:
string | undefined | null, fallback: T): T, then remove the local definitions in
TaskSettings.tsx and Board.tsx and import the shared safeJsonParse where used.
Ensure the exported function preserves the same behavior (including the
console.warn message and slice call) and TypeScript generic signature so
existing callers in TaskSettings and Board compile without changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 51185060-167a-4a88-a16e-856301146232

📥 Commits

Reviewing files that changed from the base of the PR and between df804a7 and 919eb14.

📒 Files selected for processing (2)
  • views/kanban-view-simple/src/components/Board/Board.tsx
  • views/kanban-view-simple/src/components/TaskSettings/TaskSettings.tsx

Comment on lines +21 to +29
function safeJsonParse<T>(value: string | undefined | null, fallback: T): T {
if (!value) return fallback;
try {
return JSON.parse(value);
} catch {
console.warn('Failed to parse JSON value, using fallback:', value?.slice(0, 80));
return fallback;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -E "Board\.tsx|kanban-view" | head -20

Repository: coasys/flux

Length of output: 1144


🏁 Script executed:

wc -l views/kanban-view-simple/src/components/Board/Board.tsx

Repository: coasys/flux

Length of output: 114


🏁 Script executed:

cat -n views/kanban-view-simple/src/components/Board/Board.tsx | head -40

Repository: coasys/flux

Length of output: 2066


🏁 Script executed:

cat -n views/kanban-view-simple/src/components/Board/Board.tsx | sed -n '115,130p;145,160p;158,172p;210,225p;238,255p;282,295p;313,325p;340,355p'

Repository: coasys/flux

Length of output: 6141


🏁 Script executed:

cat -n views/kanban-view-simple/src/components/Board/Board.tsx | sed -n '200,260p'

Repository: coasys/flux

Length of output: 3205


🏁 Script executed:

cat -n views/kanban-view-simple/src/components/Board/Board.tsx | sed -n '275,310p'

Repository: coasys/flux

Length of output: 1803


Add runtime type validation to safeJsonParse<T> before returning parsed values

The function catches JSON parse errors but cannot prevent valid-but-wrong JSON (e.g., objects, numbers, or non-string arrays) from breaking array operations at call sites. This is especially critical in drag-and-drop reorder logic (lines 122, 153, 166, 218, 246, 251, 289, 320, 348) where .splice(), .filter(), or .map() are called immediately on results without type guards. Add an Array.isArray() check before returning, or implement shape validation for string[] expectations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@views/kanban-view-simple/src/components/Board/Board.tsx` around lines 21 -
29, safeJsonParse<T> currently returns parsed JSON without validating its shape,
which lets valid-but-incorrect values (objects, numbers, etc.) reach callers
that immediately call .splice/.filter/.map in the board drag-and-drop logic;
update safeJsonParse to perform runtime shape checks before returning: for the
kanban use case add an overload or optional validator parameter (or a boolean
branch) that verifies Array.isArray(parsed) and that each item is a string
(e.g., typeof item === 'string') when callers expect string[]; if validation
fails, log a warning and return the provided fallback. Ensure callers (the
reorder/drag-and-drop functions that call .splice/.filter/.map) keep using
safeJsonParse<string[]>(...) so the validated type is enforced at runtime.

Comment on lines 165 to 167
const column = columns.find((col) => col.id === columnId);
const taskIds = column.orderedTaskIds ? JSON.parse(column.orderedTaskIds) : [];
const taskIds = safeJsonParse<string[]>(column.orderedTaskIds, []);
const columnTasks = await Task.findAll(perspective, { where: { id: taskIds } });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

deleteColumn can crash when column lookup misses

columns.find(...) can return undefined, but column.orderedTaskIds and column.delete() are called unguarded. Add a guard before dereference/delete.

Also applies to: 185-185

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@views/kanban-view-simple/src/components/Board/Board.tsx` around lines 165 -
167, The code calls columns.find(...) and immediately dereferences
column.orderedTaskIds and column.delete(), which can crash if the find returns
undefined; update the deleteColumn logic to check the result of
columns.find(...) (the variable column) and bail out early (return or throw a
controlled error) if it's undefined, then use column.orderedTaskIds only after
that guard (you can still call safeJsonParse on a fallback value if preferred)
and only invoke column.delete() inside the guarded block; apply the same
presence check to the other occurrence that references column (around the
Task.findAll / column.delete usage).

Comment on lines +10 to +17
function safeJsonParse<T>(value: string | undefined | null, fallback: T): T {
if (!value) return fallback;
try {
return JSON.parse(value);
} catch {
console.warn('Failed to parse JSON value, using fallback:', value?.slice(0, 80));
return fallback;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd TaskSettings.tsx

Repository: coasys/flux

Length of output: 124


🏁 Script executed:

cat -n views/kanban-view-simple/src/components/TaskSettings/TaskSettings.tsx | head -200

Repository: coasys/flux

Length of output: 8566


🏁 Script executed:

wc -l views/kanban-view-simple/src/components/TaskSettings/TaskSettings.tsx

Repository: coasys/flux

Length of output: 128


Add runtime validation to safeJsonParse<string[]> to prevent runtime crashes

JSON.parse can succeed with non-array payloads (objects, strings, numbers). The call sites on lines 73, 78, 124, and 152 immediately invoke array methods (.filter(), spread operator), which will crash if the parsed value is not an array. The current fallback of [] only activates on parse errors, not when parsing succeeds with invalid data.

Add a type guard parameter to validate the parsed value:

Proposed fix
-function safeJsonParse<T>(value: string | undefined | null, fallback: T): T {
+function safeJsonParse<T>(
+  value: string | undefined | null,
+  fallback: T,
+  isValid: (parsed: unknown) => parsed is T,
+): T {
   if (!value) return fallback;
   try {
-    return JSON.parse(value);
+    const parsed: unknown = JSON.parse(value);
+    return isValid(parsed) ? parsed : fallback;
   } catch {
     console.warn('Failed to parse JSON value, using fallback:', value?.slice(0, 80));
     return fallback;
   }
 }
+
+const isStringArray = (v: unknown): v is string[] =>
+  Array.isArray(v) && v.every((x) => typeof x === 'string');

Then update call sites to pass the validator:

-const sourceOrderedTaskIds = safeJsonParse<string[]>(source.orderedTaskIds, []).filter((id) => id !== task.id);
+const sourceOrderedTaskIds = safeJsonParse<string[]>(source.orderedTaskIds, [], isStringArray).filter((id) => id !== task.id);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@views/kanban-view-simple/src/components/TaskSettings/TaskSettings.tsx` around
lines 10 - 17, The safeJsonParse<T> helper should reject parsed non-array values
to avoid runtime crashes when callers expect string[]; modify safeJsonParse to
accept an optional type guard (validator: (v: unknown) => v is T) and after
JSON.parse run the validator and return fallback if it fails. Update all callers
that expect string[] (the safeJsonParse<string[]>() usages in TaskSettings where
the result is immediately spread or .filter() is called) to pass Array.isArray
as the validator (or a stricter element-type guard) so only actual arrays are
returned; keep the existing try/catch fallback behavior for parse errors.

Comment on lines +73 to 75
const sourceOrderedTaskIds = safeJsonParse<string[]>(source.orderedTaskIds, []).filter((id) => id !== task.id);
source.orderedTaskIds = JSON.stringify(sourceOrderedTaskIds);
await source.save(batchId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard .find(...) results before dereferencing

The code dereferences source, destination, and columnModel immediately after .find(...). If live data changes between UI action and save/delete, this will crash.

Proposed fix (defensive null checks)
 const source = columns.find((col) => col.columnName === column.columnName);
 const destination = columns.find((col) => col.columnName === taskColumn);
+if (!source || !destination) {
+  console.error('Source or destination column not found');
+  setSaving(false);
+  return;
+}
@@
 const columnModel = columns.find((col) => col.columnName === taskColumn);
+if (!columnModel) {
+  console.error('Column not found when creating task');
+  setSaving(false);
+  return;
+}
@@
 const columnModel = columns.find((col) => col.id === column.id);
+if (!columnModel) {
+  console.error('Column not found when deleting task');
+  setDeleting(false);
+  return;
+}

Also applies to: 78-80, 123-126, 151-154

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@views/kanban-view-simple/src/components/TaskSettings/TaskSettings.tsx` around
lines 73 - 75, The code assumes .find(...) returned objects (e.g., source,
destination, columnModel) and dereferences them immediately (e.g., using
source.orderedTaskIds and source.save(batchId)); add defensive null checks after
each .find call in TaskSettings.tsx and bail out or handle the missing object
(early return, user-facing error, or log and skip) before using properties or
calling methods; specifically guard the variables named source, destination, and
columnModel where they are used around the shown snippets so you never call
.orderedTaskIds, .save(...), or access columnModel properties on undefined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant