Skip to content

feat(dashboard): import a proposal from JSON when creating one - #2102

Open
brunod-e wants to merge 42 commits into
devfrom
feat/import-proposal-json
Open

feat(dashboard): import a proposal from JSON when creating one#2102
brunod-e wants to merge 42 commits into
devfrom
feat/import-proposal-json

Conversation

@brunod-e

@brunod-e brunod-e commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

What

Adds an Import JSON button to the create proposal form. It opens a modal (drawer on mobile, via the shared Modal) where you paste a proposal document, and applying it fills the title, discussion URL, description, and actions.

Why

Proposals are often drafted outside the app (forum posts, scripts, another tool's export). Re-entering four fields and every action by hand is slow and error prone, especially for multi-action proposals where a mistyped address or amount is not obvious in the form.

Format

Documented in the modal itself: the textarea placeholder shows the shape, and the body lists the fields.

{
  "title": "Proposal title",
  "discussionUrl": "https://discuss…",
  "body": "## Synopsis\n\nMarkdown description.",
  "actions": [
    { "type": "eth-transfer", "recipient": "0x… or ENS", "amount": "1.5" },
    { "type": "erc20-transfer", "recipient": "0x…", "tokenAddress": "0x…", "amount": "250000", "decimals": 6 },
    { "type": "custom", "contractAddress": "0x…", "abi": [], "functionName": "approve(address,uint256)", "args": ["0x…", "1"] },
    { "type": "custom", "contractAddress": "0x…", "calldata": "0x…" }
  ]
}

Decisions worth a look during review:

  • Every field is optional, and only the ones present are replaced. Pasting {"title": "x"} will not wipe a body you already wrote.
  • Unknown keys are stripped, not rejected, so a saved draft (which carries id, daoId, timestamps) pastes in unchanged.
  • decimals is required on erc20-transfer. Defaulting to 18 would silently scale a USDC transfer by 12 orders of magnitude, and there is no async chain read available at parse time.
  • A custom action needs either functionName + abi, or raw calldata, mirroring the cross-field rule already in ProposalFormSchema.
  • The import goes through setValue, not form.reset, so the form stays dirty and the unsaved-changes NavigationGuard stays armed.

Validation is strict on the fields it recognizes, since they feed a form that can publish an on-chain transaction, and lenient about everything else. Errors carry the path: actions[0].decimals: Required.

Changes

File
utils/parseProposalJson.ts new, zod-based parser returning a tagged result
utils/parseProposalJson.test.ts new, 17 cases
components/modals/ImportJsonModal.tsx new
components/ProposalCreationForm.tsx button + import handler
constants.ts PROPOSAL_JSON_PLACEHOLDER

The second commit slims the modal down after a first pass that carried too much reference material inline.

Verification

  • 17 new unit tests, 81 passing across the feature
  • pnpm dashboard typecheck and pnpm dashboard lint clean
  • Driven in the browser at /ens/proposals/new: a four-action document filled every field and produced the right action cards (ETH transfer, ERC-20 to an ENS name, custom-with-ABI, custom-with-calldata). Bad input showed actions[0].decimals: Required with the modal open and the form untouched. Checked the drawer at 375px.

Two things caught while verifying, both fixed here: the textarea needed a max height so a long paste or a drag on the native resize grip could not push the footer off screen, and a line break right after a <code> element was swallowing the space after it in the field list.

🤖 Generated with Claude Code

brunod-e and others added 2 commits August 2, 2026 17:47
The create proposal form gets an Import JSON button that opens a modal
(drawer on mobile, via the shared Modal) with a textarea and a written
spec of the expected document, plus a one-click example.

Applying the document fills title, discussion URL, description and
actions. Parsing is strict on the fields it recognizes since they feed a
form that can publish an on-chain transaction, and lenient about the
rest, so a saved draft (which carries id/daoId/timestamps) pastes in
as-is. Fields the document omits are left untouched, and the import goes
through setValue rather than reset so the form stays dirty and the
navigation guard stays armed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The modal was carrying a full reference document: a field table, a prose
paragraph, and a scrollable example block with a button to load it. That
is more than a modal should hold.

The format now lives in the textarea placeholder, and the body is just a
list of the fields. The reference constant shrinks to a two-action hint
and the Use example button is gone.

Two fixes found while trimming: the textarea gets a max height so a long
paste or a drag on the native resize grip can't push the footer off
screen, and the field prose is short enough to survive formatting. A
line break right after a <code> element swallows the space that follows
it, which had eaten the one in "tokenAddress and".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
anticapture-storybook Ready Ready Preview Aug 5, 2026 5:37pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
anticapture Ignored Ignored Aug 5, 2026 5:37pm

Request Review

@railway-app

railway-app Bot commented Aug 2, 2026

Copy link
Copy Markdown

🚅 Deployed to the anticapture-pr-2102 environment in anticapture-infra

Service Status Web Updated (UTC)
grafana ✅ Success (View Logs) Web Aug 5, 2026 at 5:37 pm
gateful ✅ Success (View Logs) Web Aug 5, 2026 at 5:37 pm
otelcol ✅ Success (View Logs) Aug 5, 2026 at 5:37 pm
tempo ✅ Success (View Logs) Aug 5, 2026 at 5:37 pm
loki ✅ Success (View Logs) Aug 5, 2026 at 5:37 pm
authful ✅ Success (View Logs) Web Aug 5, 2026 at 5:37 pm
prometheus ✅ Success (View Logs) Aug 5, 2026 at 5:37 pm
alertmanager ✅ Success (View Logs) Web Aug 5, 2026 at 5:37 pm
uniswap-api ✅ Success (View Logs) Aug 5, 2026 at 11:54 am
fluid-api ✅ Success (View Logs) Aug 5, 2026 at 11:54 am
scroll-api ✅ Success (View Logs) Aug 5, 2026 at 11:53 am
shutter-api ✅ Success (View Logs) Aug 5, 2026 at 11:53 am
tornado-api ✅ Success (View Logs) Aug 5, 2026 at 11:53 am
ens-relayer ✅ Success (View Logs) Aug 5, 2026 at 11:53 am
ens-api ✅ Success (View Logs) Aug 5, 2026 at 11:53 am
obol-api ✅ Success (View Logs) Aug 5, 2026 at 11:53 am
compound-api ✅ Success (View Logs) Aug 5, 2026 at 11:53 am
mcp ✅ Success (View Logs) Web Aug 5, 2026 at 11:53 am
address-enrichment ✅ Success (View Logs) Web Aug 5, 2026 at 11:53 am
gitcoin-api ✅ Success (View Logs) Aug 5, 2026 at 11:53 am
lil-nouns-api ✅ Success (View Logs) Aug 5, 2026 at 11:53 am
docs ✅ Success (View Logs) Web Aug 5, 2026 at 11:52 am
aave-api ✅ Success (View Logs) Aug 5, 2026 at 11:52 am
nouns-api ✅ Success (View Logs) Aug 5, 2026 at 11:52 am
compound-indexer-offchain ✅ Success (View Logs) Aug 3, 2026 at 10:39 pm
shutter-indexer-offchain ✅ Success (View Logs) Aug 3, 2026 at 10:36 pm
ens-indexer-offchain ✅ Success (View Logs) Aug 3, 2026 at 10:34 pm
gitcoin-indexer-offchain ✅ Success (View Logs) Aug 3, 2026 at 10:34 pm
uniswap-indexer-offchain ✅ Success (View Logs) Aug 3, 2026 at 10:34 pm
aave-indexer ✅ Success (View Logs) Aug 2, 2026 at 9:33 pm
fluid-indexer ✅ Success (View Logs) Aug 2, 2026 at 9:33 pm
lil-nouns-indexer ✅ Success (View Logs) Aug 2, 2026 at 9:33 pm
tornado-indexer ✅ Success (View Logs) Aug 2, 2026 at 9:33 pm
obol-indexer ✅ Success (View Logs) Aug 2, 2026 at 9:33 pm
gitcoin-indexer ✅ Success (View Logs) Aug 2, 2026 at 9:33 pm
shutter-indexer ✅ Success (View Logs) Aug 2, 2026 at 9:32 pm
uniswap-indexer ✅ Success (View Logs) Aug 2, 2026 at 9:32 pm
ens-indexer ✅ Success (View Logs) Aug 2, 2026 at 9:32 pm
compound-indexer ✅ Success (View Logs) Aug 2, 2026 at 9:32 pm
scroll-indexer ✅ Success (View Logs) Aug 2, 2026 at 9:32 pm
nouns-indexer ✅ Success (View Logs) Aug 2, 2026 at 9:32 pm
erpc ✅ Success (View Logs) Web Aug 2, 2026 at 9:32 pm
nodeful ✅ Success (View Logs) Aug 2, 2026 at 9:32 pm
user-api ✅ Success (View Logs) Web Aug 2, 2026 at 9:32 pm

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c8f5cb058d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/dashboard/features/create-proposal/utils/parseProposalJson.ts Outdated

Copy link
Copy Markdown
Collaborator

🎨 UI Review

Automated review · Figma: not found for this feature (see caveat below) · Spec: 📬 Proposal Improvements (Creation Flow) · Preview: not available
Caveats: ⚠️ Reviewed without a Figma reference — the linked ClickUp spec (86aje4vfh) explicitly records that the "Import JSON window" design was never produced ("Still Needs Design" list + the "Design deliverable status" note), so this review is grounded in the code, the PRD text, and UX-expert evaluation rather than a Figma comparison. The Vercel preview for the anticapture (dashboard) project is marked Ignored for this PR's commit — no live preview to click through, so mobile behavior below is reasoned from the Modal/Drawer implementation rather than confirmed visually.


Create Proposal — Import JSON row

Layout

  • The new row (ProposalCreationForm.tsx, the <div className="flex items-center justify-between gap-3"> right before the Title field) puts the helper copy and the button side by side with no responsive fallback. On narrow viewports the shrink-0 button holds its width and the paragraph ("Already have this proposal as JSON? Import it to fill the form.") gets squeezed into the remaining space, which can force it onto 2–3 cramped lines next to the button instead of reading as a single line above/beside it. Suggest stacking on small screens: flex flex-col items-start gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-3. [UX-heuristic]

Touch target

  • The "Import JSON" trigger uses size="sm" on the shared Button (py-1 px-2 + text-sm/tight ≈ 24–26px tall), well under the 44×44px minimum recommended for touch targets. It's a secondary action, but it's still a real tap target on mobile. Suggest bumping to size="md" here, or if the denser look is intentional, add min-h-11 to the button's className to keep the visual size but pad the hit area. [UX-heuristic]

Import JSON modal

  • Confirmed this composes the shared design-system Modal (apps/dashboard/shared/components/design-system/modal/Modal.tsx), which renders the mobile Drawer (bottom sheet) automatically via useScreenSize — no hand-rolled dialog. Matches the PR description.
  • Confirmed the textarea is the shared DS Textarea (.../form/fields/textarea/Textarea.tsx), including its error prop wired to border-error — not a native <textarea>. The added max-h-64 min-h-44 resize-y overflow-y-auto classes are additive and don't fight the component's own base styles.
  • font-mono text-xs on the textarea resolves to the DS --font-mono (Roboto Mono) and body-sm scale tokens, not a raw font stack — no bespoke typography introduced.
  • Icon sizing (size-3.5 on FileJson) matches the DS's own iconSizeStyles.sm convention used inside Button for size="sm" — consistent.

Question for author

  • The linked spec (86aje4vfh) describes Import JSON as offering "(a) file upload and (b) a shown format spec / prompt." This PR ships (b) only — paste-into-textarea, no file picker/drag-drop. Is the file-upload path intentionally deferred (e.g. paste covers the primary use case and is simpler/less error-prone than a drop zone), or should this PR's scope include it before merging? Worth confirming with Isadora/Alex since it's a PRD line item, not just a nice-to-have. [UX-heuristic]

No Figma-confirmed section this time — see the caveat above; there is nothing to cross-check the implementation against yet for this specific feature.


Generated by Claude Code

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

🔍 Vercel preview: https://anticapture-qsyu0nmnc-ful.vercel.app

A custom action imported with calldata "transfer(...)", or any other
non-hex string, was accepted. ProposalFormSchema then treats the action
as valid, since it only requires functionName when calldata is empty, so
the form becomes publishable and encodeActions casts the string straight
to Hex. The paste failed only once the user was already preparing the
on-chain write.

Apply the same isHex plus even-length rule the custom-action modal uses,
so the bad value is rejected while the import modal is still open and
points at actions[n].calldata.

Also validate `value` the same way: encodeActions runs BigInt() on it,
which throws on anything that isn't a plain integer, "1e18" being the
easy mistake.

This caught the modal placeholder, which writes "0x…" where real values
go and so is not a parseable document. Its test now guards the hint's
structure rather than parsing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0de3eb230

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/dashboard/features/create-proposal/utils/parseProposalJson.ts Outdated
The ABI path only checked that an abi was present. A functionName absent
from that abi, or a function imported with fewer args than it takes,
sailed through into a form ProposalFormSchema calls publishable. The
throw then landed in encodeActions during publish: the function lookup
raises outright, and a short args array reaches viem, which rejects the
arity.

Resolve the function and check the call at import time. Lookup mirrors
encodeActions (full signature or bare name) so the two never disagree on
which overload was meant, arity is compared against fn.inputs, and each
argument goes through the same argsToTrees plus isArgComplete pair the
custom-action modal uses to release its Confirm button.

The checks are skipped when raw calldata is present, matching the
precedence encodeActions already applies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb77457f49

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/dashboard/features/create-proposal/utils/parseProposalJson.ts Outdated
Comment thread apps/dashboard/features/create-proposal/utils/parseProposalJson.ts Outdated
Three ways a pasted action cleared the import and broke later.

ERC-20 decimals were taken on faith. encodeActions hands them to
parseUnits, so importing 1 USDC at decimals 18 encoded 1e18 base units
while the row still read 1: a million times the intended payment, with
nothing on screen showing the difference. decimals is now optional and
read off the token contract, the way the add-transfer modal already
does; a supplied value is treated as an assertion and rejected when the
contract disagrees. Only ERC-20 transfers need the round trip, so other
documents still import with no RPC, and a failed read blocks the import
rather than falling back to the pasted number.

A malformed ABI entry crashed the dialog. parseAbiStrict only checks for
a string type, so a { "type": "function" } with no name or inputs
survived it and reached toFunctionSignature, which throws out of
safeParse and takes the modal down. Such entries are now reported as a
paste error, and the formatter call is guarded.

Six fields the form validates went unchecked on the way in: recipient,
amount, tokenAddress and contractAddress. Because action rows render no
field errors, anything the import accepted but ProposalFormSchema
rejected showed up only as a Publish button that never enabled. The
import now reuses the form's own schemas, exported for the purpose so
the two cannot drift.

Also accept a hex `value`, which BigInt() handles and the previous
digits-only rule wrongly refused.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd7f6fd5b4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/dashboard/features/create-proposal/utils/parseProposalJson.ts Outdated
Comment thread apps/dashboard/features/create-proposal/utils/parseProposalJson.ts Outdated
Comment thread apps/dashboard/features/create-proposal/utils/parseProposalJson.ts Outdated
Comment thread apps/dashboard/features/create-proposal/components/modals/ImportJsonModal.tsx Outdated
JSON numbers past 2^53 arrive already rounded. JSON.parse turns
1000000000000000001 into 1000000000000000000 before any validation runs,
and the result looks like a clean integer, so a wei arg or value encoded
a different number than the document said. Unquoted numbers are now
refused above MAX_SAFE_INTEGER, pointing at the quoted form, which
survives intact. This covers args and amount as well as value; args is
the likeliest place to write a wei figure unquoted.

An ABI input of {} passed the well-formed check, which only looked at
whether inputs was an array. parseArrayType calls .match on the type, so
the argument walk threw a TypeError out of safeParse and unmounted the
dialog. Reached through the bare function name, which skips the
signature formatter that was already guarded. Parameters are now checked
for a string type, recursing through tuple components.

Malformed composite args were accepted as empty. storageToArg turns
unparseable JSON into an empty container, which isArgComplete calls a
complete dynamic array, and encodeActions then re-parses the original
text at publish and throws. Composite args are now required to parse as
JSON arrays before the completeness walk.

A decimals lookup outlived the modal. Cancel, Escape and the close
button all stay live while it runs, and the resolved value still called
onImport, so a cancelled paste, or the previous one after a reopen,
could overwrite the form. Each attempt takes a ticket and a stale result
is dropped; dismissing through Escape or X now routes through the same
close path as Cancel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8e47afaf69

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/dashboard/features/create-proposal/utils/parseProposalJson.ts Outdated
Comment thread apps/dashboard/features/create-proposal/utils/parseProposalJson.ts Outdated
Two gaps the previous pass left one level down, both reproduced as
failing tests before the fix.

A number nested inside a composite arg hit the same rounding as a bare
one. Checking that "[1000000000000000001]" parses to an array said
nothing about its leaves, and JSON.parse had already flattened the last
digit, so publish re-parsed the same text and encoded the rounded
figure. Composite values are now walked for numbers past
MAX_SAFE_INTEGER, pointing at the quoted form the way scalars already
did.

A tuple input with no components passed as well formed. The completeness
walk reads components as an empty list, so a struct with no fields calls
any "[]" complete, while encodeActions refuses the same parameter
outright and throws mid-publish. Tuples, including tuple arrays at any
depth, now have to declare components, and those components are checked
recursively like any other parameter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8535f72237

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Two things, both in the conversion layer between a stored arg and calldata.

The safe conversion is now the default one. `storageToArg` and `argsToTrees`
are the strict pair that refuses anything the ABI cannot hold, and the
forgiving pair is `storageToArgForDisplay` and `argsToTreesForDisplay`, named
for the only thing that answer is good for. Every bug in this file came from a
caller reaching for the forgiving variant to decide whether an action was
complete, so the plain name is the one that fails closed and the other has to
be asked for. The custom action modal now draws its inputs from the display
conversion and gates its Add button and calldata preview on the strict one,
which it was not doing.

And the utils are smaller. The JSON scanner loses its class and tracks only
numbers, which are the only values the parser rejects for being unquoted, so it
still reports the line and the digits as written for a third of the size.
importIssueCopy was three string formatters with one consumer, so it lives in
that consumer now. importedArgs loses the per-failure object literals to three
helpers, without changing a message. Comments across all of them are cut back
to invariants, from up to 75 percent of a file down to the repo's own range.

  scanJsonSource   316 to 153, plus 200 to 110 in its test
  importedArgs     299 to 215
  importHandoff     76 to  49
  address           29 to  11
  importIssueCopy  149 to   0, deleted with its test

No behaviour change beyond the modal gate: 463 tests pass, and the three that
asserted forgiveness now ask for it by name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
chatgpt-codex-connector[bot]

This comment was marked as outdated.

brunod-e and others added 2 commits August 5, 2026 07:36
`argsToTrees` mapped over the ABI's inputs alone, so a missing arg read as
"" and an extra one was dropped. An action whose args and function drifted
apart in a draft that never passed `ProposalFormSchema` therefore published
calldata its row never described: `setMessage(string)` with `args: []`
encoded an empty string instead of failing. Same fail-closed reasoning as
the tuple arity check in `coerceStrict`, one level up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The comment described `storageToArg` as forgiving and referred to
`argsToTreesStrict`; both names moved when the strict variants became the
plain ones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8b7e4d287c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

An import stashes before it navigates, because a sign-in can leave the page
and the values have to survive it. Signed out, `goToNewProposal` only opened
the sign-in modal, and dismissing it cleared the redirect while leaving the
stash: the next "Create new" for that DAO in the tab drained it and filled a
form that was asked to be blank, announcing an import the author had walked
away from and arming the dirty flag NavigationGuard reads.

`openLogin` now takes an `onDismiss`, so whoever staged for the post-sign-in
route owns undoing it. Deferring the stash instead was not available: magic
link and OAuth unload the page, which is why the handoff is sessionStorage
rather than state.

The undo is not gated on there being no session. `LoginProvider` reads
`useSession` while its callers gate on `useAuthSession`, and the two disagree
for a stale wallet session — which an open modal actively keeps alive, since
`authFlowActive` stands the sign-out effect down. Gating would have skipped
the undo in exactly the case the caller was signed out from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b266d1fbe8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/dashboard/features/create-proposal/utils/importHandoff.ts Outdated
The magic-link sign-in mails an absolute URL back to the creation form, and
following it from a mail client opens a new browsing context. sessionStorage
is per-context, so `takeImportedProposal` found nothing there: the author
signed in successfully and landed on a blank form with their document gone
and nothing said. Surviving a sign-in that leaves the page is the whole job
of this module, and only cross-context storage does it.

The comment above `keyFor` had justified sessionStorage over the URL, which
is a question about size, and that was mistaken for having settled the
question about scope.

localStorage costs what sessionStorage was buying: any tab can drain the
stash, and it outlives the tab that wrote it. So the record now carries its
write time and is refused after an hour — long enough for a mail round trip,
short enough not to fill a form in a later session that nobody asked to
fill — and is cleared whether it was accepted, expired, or malformed. Two
tabs importing for the same DAO share the key and the last write wins, which
is accepted and written down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 96aecf5984

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/dashboard/shared/utils/address.ts
brunod-e and others added 2 commits August 5, 2026 08:37
# Conflicts:
#	apps/dashboard/features/create-proposal/schema.ts
…e style

AGENTS.md asks for arrow functions; `createMemoryStorage` in the import handoff
test was the one `function` declaration this PR adds, so it is converted. The
other declarations in these files come from dev untouched and are left alone.

Comment blocks were the bigger drift: 27 of them ran past 8 lines, against a
ceiling of 3-8 in dev's own version of the same files. All 26 that this PR
introduced are cut to the essential reason; the 16-line block in LoginProvider
is dev's and stays as it is.

Comments and one declaration keyword only — no behaviour change. 487 tests,
typecheck and lint all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 21ceb96949

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/dashboard/features/create-proposal/utils/importedArgs.ts Outdated
The JSON import restated rules the form already owned, and the parameter
walk they both need was written out at every call site. Three passes
answered "can this argument be encoded as what it claims to be" —
isArgComplete for the editor, tupleArityError for a better arity message,
and the import's own translation — and they disagreed on arity wording and
on whether an empty dynamic array counted as filled in.

- argIssues is now that single answer, and isArgComplete is it as a
  boolean. tupleArityError and the composite pre-pass are gone.
- customActionIssues moves from ProposalFormSchema's superRefine onto
  ProposalActionSchema, so the import dialog reaches it before the form
  exists. A paste naming a function that isn't in its ABI is refused in
  the dialog instead of turning up later as a Publish button that will
  not enable.
- The import's parallel action schemas are derived from the form's
  members (PendingProposalActionSchema) rather than kept in step by hand.
- shapeOf replaces the array/tuple/leaf preamble that ten walkers each
  spelled out, and arityError is the one place a container's declared
  size is compared with what it holds.
- Four identical {path, message} types collapse to one Issue.

The Solidity type grammar moves to abitype's zod schemas, which viem is
already built on: uint257, uint255, bytes33, bytes0, fixed128x18 and a
tuple with no components were all being caught by hand-written regexes.
Readability stays a separate, looser check, because judging it with the
grammar drops the offending function from the ABI list and loses the
message that names the bad type. `function` stays manual — legal ABI that
viem's encoder refuses.

Numbers no longer have to arrive quoted. The document is read through
jsonc-parser, so 1000000000000000001 and 1.000000000000000001 survive as
the text they were written as instead of as rounded doubles, which is what
the quoting rule existed to work around. That also retires the
hand-written source scanner: line numbers now come from the syntax tree,
for every path rather than only for figures.

One consequence worth naming: a number where text belongs is now read as
that text, so {"title": 42} imports a title of "42" rather than being
refused.
Both already resolved in the tree — abitype as viem's own dependency,
jsonc-parser transitively — so this only declares them.
987 comment lines across the feature, 19% of everything the branch added.
Most of it narrated the code beside it or recorded what the code used to
be, which is review context with a shelf life, not something the next
reader needs.

What stays is the set of things that cost a bug or would cause one: a
pasted `decimals` reaching `parseUnits`, raw calldata winning over
`functionName` in the encoder, the modal's ticket against a stale decimals
lookup, `storageToArg` vs the forgiving variant, the checksum-agnostic
resolver, `function` being legal ABI that viem refuses, and why the
document is not read with `JSON.parse`. One or two lines each, on the line
they guard.

Comments now 164 of 4,303 added lines (3%): 144 in production, 20 in
tests, where a name already says what a case is for. No code changed —
521 tests pass, lint clean, tsc identical to baseline.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 906fc445aa

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

router.push(`${basePath}/proposals/new`);
openLogin({
redirectTo: target,
onDismiss: () => clearImportedProposal(daoId),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the import stash while email sign-in is pending

When an unauthenticated import uses the Email path, LoginModal moves to its “sent” screen after sending the magic link but no session exists yet; if the author closes that screen before opening the emailed link, LoginProvider treats it as a dismiss and this callback deletes the localStorage handoff. The magic-link callback then reaches /proposals/new with nothing for takeImportedProposal() to consume, so preserve the stash after a link has been sent or clear it only on true cancellation.

Useful? React with 👍 / 👎.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants