Skip to content

fix(worklist): tolerate invalid shared queries - #156

Merged
wsxiaolin merged 2 commits into
mainfrom
codex/fix-worklist-share-payload
Jul 31, 2026
Merged

fix(worklist): tolerate invalid shared queries#156
wsxiaolin merged 2 commits into
mainfrom
codex/fix-worklist-share-payload

Conversation

@16th-admin

Copy link
Copy Markdown
Collaborator

Summary

Treat malformed work-list route payloads as an empty query instead of allowing atob or JSON parsing to fail during rendering.

Root cause

A public URL such as /#/l/not-base64 was decoded directly from the template and computed state. Invalid Base64/JSON threw a browser exception and surfaced an error dialog.

Validation

  • npm run build
  • TypeScript build completed successfully

This PR intentionally only handles malformed payloads; query-change pagination is a separate fix.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Codex-Style Code Review by OpenCode

💡 This is an automated advisory review. It is non-blocking and does not affect merge requirements.

PR Review: src/services/utils.tsdecodeHrefToQueryObj

Summary

Wraps the base64/JSON decoding logic in a try/catch and adds a type guard on the parsed result. The change correctly handles throwable operations (atob, JSON.parse) and prevents non-object results from propagating.


🔴 [blocking] Empty catch {} silently swallows all errors

The bare catch {} discards every error (invalid base64, malformed JSON, TextDecoder failures) with zero observability. If this function ever receives unexpected input in production, there is no way to diagnose it — no log, no counter, no trace.

Why this matters: The function is a data-decoding utility. Silent corruption here could cause subtle downstream bugs (empty query objects where real data was expected) that are extremely hard to root-cause.

Fix suggestion: At minimum, log a warning:

} catch (e) {
  console.warn('decodeHrefToQueryObj: failed to decode input', e)
  return {}
}

Or better, let callers decide by not catching at all and propagating the error — or using a Result type.


🟡 [important] for...in should be Object.keys() / Object.entries()

for (const k in result) with hasOwnProperty is correct but unnecessarily verbose and risks confusion. JSON.parse returns a plain object with no prototype enumerable properties, so for...in is safe here — but the guard is still defensive. Cleaner alternatives:

// Option A
for (const [k, v] of Object.entries(result)) {
  if (Array.isArray(v) && v.join('').includes(',')) {
    result[k as keyof typeof result] = v[0].split(',')
  }
}

// Option B (immutable)
return Object.fromEntries(
  Object.entries(result).map(([k, v]) => [
    k,
    Array.isArray(v) && v.join('').includes(',') ? v[0].split(',') : v,
  ])
)

🟡 [important] No return type annotation

The function signature has no explicit return type. With JSON.parse, TypeScript infers any, which defeats type safety for callers. Consider:

export function decodeHrefToQueryObj(base64Input: string): Record<string, unknown> {

🟢 [nit] Redundant typeof guard

typeof base64Input !== 'string' is unnecessary next to !base64Input — the truthiness check already covers '', null, undefined. If the parameter is typed string, the whole guard is a no-op (only needed if called from JS or with any).


🟢 [nit] Mutating the parsed object

The function mutates result[k] in place. While safe here (freshly allocated from JSON.parse), it's an unnecessary side-effect pattern. An immutable transform (see suggestion above) would be more predictable.


Verification

Check whether useUnknownInCatchVariables is enabled in tsconfig.json. If it is (recommended by TS 5.x strictness), the catch {} without a binding is fine; if it's not, consider enabling it for better error handling practices.


Decision: 💬 Comment

The core improvement (error wrapping) is solid, but the silent catch needs addressing before this is production-safe. The type guard on result is a nice addition.

@wsxiaolin
wsxiaolin marked this pull request as ready for review July 31, 2026 15:07
@wsxiaolin
wsxiaolin merged commit 226f589 into main Jul 31, 2026
5 checks passed
@wsxiaolin
wsxiaolin deleted the codex/fix-worklist-share-payload branch July 31, 2026 15:07
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Codex-Style Code Review by OpenCode (Vue Best Practice Enabled)

💡 Autonomous AI Reviewer inspecting git commit history and Vue code quality.

Review complete. Report written to review_report.md.

PR #156fix(worklist): tolerate invalid shared queries — 3 commits, only src/services/utils.ts changed (+17/−10).

Verdict: Approve, no blocking issues.

  • The fix is sound: try/catch now covers the whole decode pipeline (atobTextDecoderJSON.parse → mutation loop), and the new guard rejects null/primitives/arrays from JSON.parse, fixing a latent strict-mode TypeError when decoded JSON was "123"/"abc".
  • console.warn in catch (e) is idiomatic here (advancedParser.ts:51, sw.ts:55; no-console is off), and {} return is safe for the caller (...q spread at wortList.vue:66).

Follow-ups noted (non-blocking):

  1. WorkList.vue:18 re-decodes inline on every render while decodedQuery computed already caches the same value — reuse the computed.
  2. Function return type is implicit any; suggest Record<string, unknown>.
  3. Pre-existing v.join('').includes(',') comma-split heuristic drops items in ["a,b","c"] — now silently masked by the try/catch.
  4. No unit-test infra exists; a small regression test for invalid inputs would lock this in.
    )** is consistent with existing patterns (advancedParser.ts:51, sw.ts:55) and no-console` is disabled in eslint config, so it won't
    break CI.
  • Return shape ({} / object) matches the q?: Partial<ExperimentQuery> prop contract.

Issues / suggestions

1. Redundant double-decode in WorkList.vue (pre-existing, but this PR owns the flow)

WorkList.vue:18 calls decodeHrefToQueryObj(route.params.config) directly in the template on
every re-render, while decodedQuery computed (:38) already decodes the same value and is
cached. Since the PR touches exactly this decoding path, consider binding :q="decodedQuery" and
dropping the inline call — removes duplicated work per render and one source of truth.

2. No explicit return type

With multiple return {} paths added, the function still has an implicit any return
(from JSON.parse). Annotate : Record<string, unknown> (or a dedicated type) so callers don't
silently receive any — cheap insurance given the mutation loop edits values by index.

3. Comma-split heuristic is fragile (pre-existing, noted for follow-up)

v.join('').includes(',') then v[0].split(',') drops elements in multi-item arrays containing
a comma (["a,b","c"]["a","b"], "c" lost). Not introduced here, but the try/catch now
masks it as a silent {} for some inputs; consider v.some(s => s.includes(',')) + a stricter
split in a future pass.

4. Behavior change to document

Valid JSON that decodes to a non-object (number/string) now silently yields {} instead of the
old (undefined/throwy) behavior. This is correct for the worklist use-case, but worth a comment
in the JSDoc since it changes the contract for any future callers.

5. Tests (optional)

There is no unit-test runner (Playwright e2e only). A small node script / e2e case exercising
invalid base64, valid-base64-of-non-object, and Tags with commas would lock in this fix.

Verdict

Solid, minimal, defensive fix. The core try/catch + type guard correctly resolve the crash for
invalid shared queries, and the console.warn addition is idiomatic for this repo. No blocking
issues — items 1–2 are small follow-ups in the same area.

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.

2 participants