fix: resolve SonarQube issues and align mobile with web parity - #8
Merged
Conversation
SonarQube fixes (web): - S6754: Replace unused useState with useMemo for showGeneralOnToday - S6754: Rename searchQuery to localSearchQuery for symmetric naming - S3776: Extract helpers to reduce extractFetchError cognitive complexity - S6847: Remove unnecessary onClick/onKeyDown from dialog element Mobile parity fixes: - use-habits: Add linked goal updates, gamification XP, profile streak on log - use-habit-form: Support configurable weekStartDay (was hardcoded Monday) - use-retrospective: Use i18n locale instead of hardcoded English - use-tag-selection: Align maxTags default to 10 (was 5) - use-time-format: Fix async-in-useMemo bug, use useEffect instead - ui-store: Add manuallySelectedIds, toggleSelectionCascade, selectAllHabits, showCreateGoalModal to match web store - query-client: Align staleTime (5m), gcTime (24h), retry logic with web - Add missing lib files: plural.ts, habit-request-builders.ts, habit-optimistic-helpers.ts - support: Add name/email fields, profile pre-fill, validation, i18n - privacy: Replace hardcoded English with i18n keys - retrospective: Add period selector, generate button, cache indicator - preferences: Fix "Portugues" typo to "Português" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
thomasluizon
added a commit
that referenced
this pull request
Apr 15, 2026
useHabitDetail (HabitDetail shape) and useHabitFullDetail (HabitFullDetail shape) both cached under habitKeys.detail(id). Whichever hook ran second won the cache entry; the other consumer then read mis-typed data, risking runtime crashes where fields were missing. Adds habitKeys.fullDetail(id) and retargets useHabitFullDetail on web + mobile. Cache invalidations in use-habits and habit-mutation-helpers now invalidate both keys on habit mutation. Covers frontend plan P0 #3 / Area C #8. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 tasks
40 tasks
This was referenced Jun 27, 2026
Merged
thomasluizon
added a commit
that referenced
this pull request
Jun 27, 2026
…olish (#8, #13) (#326) Year-skip arrows on month nav (both platforms). Month-stats redesigned as a data-driven extensible StatTile grid. Web: view switcher (Month/Week/Range) + Google-Calendar-style time-grid (hourly rows, day columns, timed-habit blocks, all-day band, now-line) for week + custom contiguous-range; mobile keeps month. Sync screen: Sincronizar agora->Sincronizar, deselect-all text->icon button. web+mobile parity for shared parts; grids web-only by design; both locales. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 27, 2026
This was referenced Jul 6, 2026
thomasluizon
added a commit
that referenced
this pull request
Jul 10, 2026
…443) Records the section-A two-layer config work (agentic-dev-workflow PR #8) as Stage 7j: a global ~/.claude/workflow.config.yaml defaults layer + project overrides (project wins), the lossless split, global generation, and machine- wide enforcement wiring. This was specced in section A but never scoped into the original 7a-7i checklist — a real gap, now closed. Documentation-only here. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jul 10, 2026
This was referenced Jul 13, 2026
thomasluizon
added a commit
that referenced
this pull request
Jul 23, 2026
…instead Fourth review round found bypass #8: joinConcatenatedLiterals needed the quote character to match on both sides of the +, so a mixed-quote split walked past it. Verified, and it is worse than reported - two more shapes I went looking for also walked past: splitting the path across variables, and assembling it with Array.join. The suggested fix (drop the backreference) closes only the first. That is the whole lesson of the last four rounds. Each one closed a different spelling of "hide the path" - an alias, a split literal, a mixed quote pair, a variable - and the next round found the next spelling. Deciding what a path argument evaluates to inside arbitrary code is not something a regex can do, so the path is no longer what decides. An interpreter one-liner that cannot be PROVEN a pure read is now refused whatever it names. This is not a new doctrine, it is the one this file already states - the code is judged by an allowlist of what it may call - applied at the point that previously required a path rule to fire first. There is nothing left to obfuscate, because the filename stopped mattering. The cost is honest and worth naming: a legitimate `node -e` that writes any file is now refused and must use the Write tool, and a `node -e` whose require() argument is itself concatenated is refused one step earlier than before (it was already unverifiable). The reads this guard advertises still work: node -p of the manifest, a bound module ref used for a direct read, cat/jq/grep, and the Read tool. Twenty-three write shapes across five adversarial batteries are refused. 761 assertions, 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pgh7M5YPWLyoDmZpWSTDLt
thomasluizon
added a commit
that referenced
this pull request
Jul 23, 2026
* chore(harness): bring the /drive work-order harness to main The harness that hands an unattended agent a scoped work order and then measures whether it actually did the work has lived only on feature/539-b5-apply-design. Nothing on main could run it, so /drive was usable for exactly one issue. This ports the tooling itself. What lands: - tools/workorder.mjs, drive-queue.mjs, check-diff-ownership.mjs, surface-manifest.mjs, check-surface-coverage.mjs, visual-signature.mjs and the visual-capture helpers - .claude/skills/drive/run.mjs, the engine SKILL.md references but main lacked - forbid-gate-tamper + surface-coverage-gate hooks and their shared rule core - the context reorganisation: .claude/rules/core.md stays always-loaded, activity-scoped judgement moves to .claude/playbooks/ - a surfaces manifest and 169 work orders derived from MAIN's own router and component graph, not the design branch's Five independent review rounds hardened this before it moved. The load-bearing outcomes: the ownership gate judges an explicit base...head range and refuses to guess one, so a child that commits an escape and checks out the base branch no longer scores clean; an empty range is a failure, not a pass, which is what stops a child that produced nothing from being recorded ready-for-review; and debt clearance is labelled a lint-count axis with the depth measurement printed beside it, because clearing lint is not evidence that a pixel moved. Deliberately NOT included, because they are #539 design work rather than harness: the spacing-scale and no-arbitrary-zindex lint rules, the eslint config changes that flip no-decorative-glow and no-raw-gradient to error (main still has the glow those rules ban), and the populated suppressions ledgers. The ledgers land empty so the tools have their required input; they fill when the rules arrive with the design work. Two fixes main needed to accept the port: - the typed-uppercase corpus guard becomes a ratchet instead of asserting zero. main carries two pre-existing values per locale, and one of them, confirmPlaceholder "ORBIT", is load-bearing: the user types it verbatim to confirm account deletion, so it must never be recased. - tools/visual-signature.mjs carried a stray NUL byte that made git treat a source file as binary and its diff unreviewable. 733 hook assertions pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pgh7M5YPWLyoDmZpWSTDLt * fix(harness): close the alias bypass in the tamper guard and finish the port's deletions Addresses the three findings from the review of this PR. Critical - the signoff tamper guard allowlisted a call by the identifier at the call site, never by what that identifier was bound to. Since the allowlist has to carry generic names for their legitimate uses (map, filter, parse, test), `const map = require("fs").writeFileSync; map(signoff, "fake")` walked straight through. I reproduced all four shapes the reviewer traced by hand, plus a destructured rename and a computed member, before changing anything: four of five were ALLOWED. Now the member lifted off a MODULE must itself be a read API, a computed member is refused because the name is not visible, and a destructured binding is judged by the keys it lifts rather than the names it gives them. A required .json stays DATA, so `require("./surfaces.json").cells` still reads - that is the pattern this hook's own message advertises, and over-blocking it is what tempts a future session to disarm the hook. Renaming even a read API is refused, fail-closed: the guard judges names, and the Read tool covers the rest. Six regression tests pin every shape; the suite is 740 assertions, 0 failures. Medium - the reviewer found flag-new-todos.mjs left behind with an import of checkNewTodos, which this port removes. The cause was broader than the one file: `git checkout <ref> -- <dir>` adds and updates but never deletes, so every path the harness branch had removed survived the port. That was 18 files, not one: the dead hook, the rules README and the three rule files that became playbooks, the /execute and /night-run skills that /drive replaces, and the eight .opencode/agents mirrors whose deletion is a locked decision the ported CLAUDE.md already documents. eslint-rules/no-draggable-onscroll.cjs is deliberately KEPT - it is main's own rule from #568, absent from the branch only because the branch predates it. Low - removed the stale comment claiming the locale corpus carries zero typed-uppercase violations, which contradicted the ratchet directly below it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pgh7M5YPWLyoDmZpWSTDLt * fix(harness): track the module reference, not the shape of the require call Second review round on this PR found bypass #6: the fix for #5 only recognised a member chained straight onto require(), so splitting it across two statements put the write behind an allowlisted call-site name again. node -e "const m=require('fs'); const map=m.writeFileSync; map(signoff,'{}')" Reproduced before changing anything: three of four write shapes were ALLOWED, and the reviewer traced it by hand because its sandbox could not execute node. The one shape already blocked was blocked for an unrelated reason. The classifier now tracks the module REFERENCE rather than the syntax around it. An identifier bound to a require() of a module is followed through plain rebinding, so a chain of aliases inherits it, and every member lifted off any of them must be a read API. A computed member is refused because the name is not visible. A required .json stays DATA, so reading .cells off the manifest still works. Renaming even a read is still refused by the callee scan: this list grants nothing, it only withholds. Replaced the per-identifier dynamic RegExp with static patterns after the first attempt built its source in a template literal, where \b and \s are JS escapes, not regex ones - it threw at parse time and every command sailed through while it did. Static patterns cannot fail that way. Five regression tests cover the two-step, transitive-rebind, computed-member and destructure-off-a-bound-ref forms, plus the honest read that must keep working. 745 assertions, 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pgh7M5YPWLyoDmZpWSTDLt * test(harness): pin the rest of the tamper-bypass class The reference tracking that closed bypass #6 should make the neighbouring shapes fall out for free. Probed rather than waited for: write streams, deletions, the promises submodule off fs, fs/promises, child_process, and a path built by concatenation are all refused. Pinned so a later change to the classifier cannot quietly reopen one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pgh7M5YPWLyoDmZpWSTDLt * fix(harness): judge the path by the value it builds, and fail the fence closed Third review round on this PR found bypass #7, and it was right about my own test too. Every path rule matches a CONTIGUOUS literal in the raw command text, so splitting the path across concatenated string literals means no rule fires and the segment is never examined at all. The test I added last round fragmented only the FILENAME, so the directory rule still matched an untouched token - it passed for the wrong reason and left the hole open. Verified before fixing: three of four fragmentation shapes were ALLOWED, including writes to both signoff.json and surfaces.json. Adjacent same-quoted literals are now joined before the path rules run, so the path is judged by the value it builds rather than the way it is spelled. Separately, and worse: the adapter caught every internal error and exited 0. A fence that opens when its own code throws is not a fence. This was not theory - my first attempt at the #6 fix built a regex inside a template literal, where \b and \s are string escapes rather than regex ones. It threw on every call, and every payload sailed through while it did. The catch now exits 2 with an explanation, so a command the guard cannot classify is refused rather than waved through. Proven by stubbing the rule core to throw. Also carried forward from the review: WORKFLOW.md named the deleted /execute skill and still described bundles as opening draft PRs, which contradicts the ready-for-review canon; and the flag-new-todos removal left an orphaned fixture whose MARK constant no longer existed. Twenty write shapes across four adversarial batteries are refused; five honest reads still pass, including require('./x.json').cells and a jq read. 755 assertions, 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pgh7M5YPWLyoDmZpWSTDLt * fix(harness): stop matching obfuscated paths; refuse the interpreter instead Fourth review round found bypass #8: joinConcatenatedLiterals needed the quote character to match on both sides of the +, so a mixed-quote split walked past it. Verified, and it is worse than reported - two more shapes I went looking for also walked past: splitting the path across variables, and assembling it with Array.join. The suggested fix (drop the backreference) closes only the first. That is the whole lesson of the last four rounds. Each one closed a different spelling of "hide the path" - an alias, a split literal, a mixed quote pair, a variable - and the next round found the next spelling. Deciding what a path argument evaluates to inside arbitrary code is not something a regex can do, so the path is no longer what decides. An interpreter one-liner that cannot be PROVEN a pure read is now refused whatever it names. This is not a new doctrine, it is the one this file already states - the code is judged by an allowlist of what it may call - applied at the point that previously required a path rule to fire first. There is nothing left to obfuscate, because the filename stopped mattering. The cost is honest and worth naming: a legitimate `node -e` that writes any file is now refused and must use the Write tool, and a `node -e` whose require() argument is itself concatenated is refused one step earlier than before (it was already unverifiable). The reads this guard advertises still work: node -p of the manifest, a bound module ref used for a direct read, cat/jq/grep, and the Read tool. Twenty-three write shapes across five adversarial batteries are refused. 761 assertions, 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pgh7M5YPWLyoDmZpWSTDLt * fix(harness): let the plan un-ignore reach .claude/plans/completed Review found that `.claude/plans/*` plus `!.claude/plans/*.plan.md` cannot re-include a plan inside a subdirectory: git never descends into an excluded directory to evaluate a nested negation, so the `completed` entry was excluded whole. That is exactly where implement/SKILL.md files a finished plan (`mkdir -p .claude/plans/completed && mv ...`), and pr-review/SKILL.md reads it back, so a completed plan would silently never be committed - recreating the fresh-checkout-with-a-missing-contract failure the un-ignore was added to fix. Verified with git add --dry-run rather than check-ignore, whose exit code is ambiguous when the last matching rule is a negation: before, a top-level plan staged and .claude/plans/completed was reported ignored; after, both stage. A non-plan file in the same directory stays ignored, which is the half worth keeping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pgh7M5YPWLyoDmZpWSTDLt * fix(harness): give PAUSED the same shape as the other four gate files Review found the best defect of this whole series, because it needs no cunning. PAUSED was the only one of the five path patterns without an optional directory prefix, and the only one tested against the whole raw command rather than per segment. So `cd .claude/manifests && touch PAUSED` matched nothing: the cd segment is a read, and the touch segment carries no directory text at all. That one command silently creates the human-only disarm switch, and the Stop gate exits immediately when it exists. Verified: four of five shapes were allowed, including `cd ... && echo x > PAUSED`. PAUSED now carries the same optional-prefix bare-name shape as signoff, surfaces and verdicts, and is judged per segment like them. Reads of it are allowed as a consequence, which also retires a false positive that blocked an ordinary `[ -f ... ]` existence test earlier in this session - a guard that refuses honest reads is one a future session disarms. The same review noted the sibling: plain shell adjacent-quote concatenation (`sign''off.json`, which the shell parses identically) split the literal for the other four. Empty quote pairs are now spliced out before path matching, next to the JS concatenation join. Also: WORKFLOW.md described /drive output as draft PRs in seven places, directly contradicting its own top table and the implement agent specs, which state that a draft PR silently skips reviewers. All seven now say ready-for-review. Five regression tests. 766 assertions, 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pgh7M5YPWLyoDmZpWSTDLt --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Changes
SonarQube (web)
page.tsx: Replace unuseduseStatewithuseMemo, rename local search state for symmetric naminglogin/page.tsx: Extract helpers to reduceextractFetchErrorcognitive complexity from 33 to ~6app-overlay.tsx: Remove unnecessary event handlers from<dialog>elementMobile Parity
Test plan
npx turbo run type-check --filter=@orbit/web)🤖 Generated with Claude Code