JUST FOR CI RUN: feat: custom proposal templates (authoring + fill + opt-in gating) - #997
Closed
frol wants to merge 3 commits into
Closed
JUST FOR CI RUN: feat: custom proposal templates (authoring + fill + opt-in gating)#997frol wants to merge 3 commits into
frol wants to merge 3 commits into
Conversation
Reusable templates for arbitrary FunctionCall proposals: a manifest DSL, Visual/Code authoring, a generated fill form, and per-treasury opt-in. ChangePolicy-gated writes; the produced proposal is an ordinary FunctionCall. Includes nt-be CRUD API + tests, nt-fe engine/UI + unit + e2e, docs and an authoring skill. Logging uses tracing to match the backend.
Contributor
There was a problem hiding this comment.
Pull request overview
Introduces an opt-in “Custom Requests / Proposal Templates” feature that lets DAOs author manifest-driven reusable forms which file arbitrary SputnikDAO FunctionCall proposals, spanning new frontend authoring/fill UI, sidebar/navigation surfaces, and backend storage + CRUD/flag endpoints.
Changes:
- Added a proposal-template manifest DSL (frontend validation + placeholder engine) and a Code/Visual authoring experience, plus a runtime form renderer to file proposals from templates.
- Added an opt-in per-treasury “Custom Requests” flag (Settings → Developer), onboarding tour cue, and a sidebar “Request Templates” section with pinned templates.
- Added backend migrations + endpoints for proposal template CRUD and the custom-requests flag, plus supporting test utilities and auth error mapping improvements.
Reviewed changes
Copilot reviewed 56 out of 64 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| skills/trezu-custom-proposal-template/SKILL.md | Portable skill prompt for generating manifest JSON without repo access |
| skills/trezu-custom-proposal-template/README.md | Installation/usage instructions for the portable skill |
| nt-fe/features/proposal-templates/types.ts | Frontend ProposalTemplate DTO type |
| nt-fe/features/proposal-templates/manifest.ts | Zod schema + placeholder extraction/substitution + manifest parsing utilities |
| nt-fe/features/proposal-templates/manifest.test.ts | Unit tests for manifest validation/parsing/placeholder behavior |
| nt-fe/features/proposal-templates/hooks/use-proposal-templates.ts | React Query hook for listing templates (session/guest gated) |
| nt-fe/features/proposal-templates/hooks/use-proposal-template-mutations.ts | Create/update/delete mutations with targeted cache invalidation |
| nt-fe/features/proposal-templates/hooks/use-custom-requests-enabled.ts | React Query hook + mutation for the opt-in feature flag |
| nt-fe/features/proposal-templates/form-schema.ts | Builds RHF/Zod schema + default values from a validated manifest |
| nt-fe/features/proposal-templates/form-schema.test.ts | Unit tests for form-schema validators/default coercion |
| nt-fe/features/proposal-templates/error-map.ts | Maps path: message errors to inline UI messages / routing rules |
| nt-fe/features/proposal-templates/error-map.test.ts | Unit tests for error routing/cleaning logic |
| nt-fe/features/proposal-templates/draft.ts | Visual-builder draft model + lossless manifest round-tripping + normalization |
| nt-fe/features/proposal-templates/draft.test.ts | Unit tests for draft round-tripping + normalization rules |
| nt-fe/features/proposal-templates/components/visual-builder.tsx | Visual authoring UI sections + error gating/routing |
| nt-fe/features/proposal-templates/components/touched-gating.test.tsx | Tests for “no errors until touched” behavior on initial render |
| nt-fe/features/proposal-templates/components/template-editor.tsx | Shared create/edit editor with Code/Visual toggle + validation |
| nt-fe/features/proposal-templates/components/manifest-form.tsx | Runtime manifest-to-form renderer for filing proposals |
| nt-fe/features/proposal-templates/build-proposal.ts | Engine building { kind, description } from manifest + filled values |
| nt-fe/features/proposal-templates/build-proposal.test.ts | Unit tests for interpolation + proposal building/tagging |
| nt-fe/features/proposal-templates/args-node.ts | Args-tree editor helpers (type selection, dynamic arg detection, dupes) |
| nt-fe/features/proposal-templates/args-node.test.ts | Unit tests for args-node helpers |
| nt-fe/features/proposal-templates/api.ts | Axios API client for templates + custom-requests endpoints |
| nt-fe/features/onboarding/steps/page-tours.tsx | Added Request Templates onboarding tour definition/IDs |
| nt-fe/features/onboarding/steps/index.tsx | Registered the new tour in the global tours list |
| nt-fe/components/sidebar.tsx | Added Request Templates nav section + pinned templates subtree |
| nt-fe/components/animate-ui/icons/code-xml.tsx | Added animated CodeXml icon for the sidebar entry |
| nt-fe/app/(treasury)/[treasuryId]/settings/page.tsx | Added Developer tab visibility logic tied to Custom Requests opt-in |
| nt-fe/app/(treasury)/[treasuryId]/settings/components/developer-tab.tsx | Developer tab UI to enable/disable Custom Requests + trigger tour |
| nt-fe/app/(treasury)/[treasuryId]/custom-templates/layout.tsx | Metadata/layout wrapper for the custom templates section |
| nt-fe/app/(treasury)/[treasuryId]/custom-templates/page.tsx | Templates index page with pin/edit/delete actions |
| nt-fe/app/(treasury)/[treasuryId]/custom-templates/create/page.tsx | Create template page using shared editor + create mutation |
| nt-fe/app/(treasury)/[treasuryId]/custom-templates/[slug]/page.tsx | Fill-and-file page resolving template by manifest slug |
| nt-fe/app/(treasury)/[treasuryId]/custom-templates/[slug]/edit/page.tsx | Edit/delete template page using shared editor + update/delete mutations |
| nt-be/src/handlers/proposal_templates.rs | Backend CRUD handlers + structural manifest validation/normalization |
| nt-be/src/utils/test_utils.rs | Added shared policy/treasury/auth test scaffolding helpers |
| nt-be/src/routes/mod.rs | Wired proposal-templates and custom-requests routes |
| nt-be/src/handlers/treasury/policy.rs | Centralized treasury policy cache key for reuse in tests |
| nt-be/src/handlers/treasury/mod.rs | Registered the new custom_requests handler module |
| nt-be/src/handlers/treasury/custom_requests.rs | Backend GET/PUT for Custom Requests flag + tests |
| nt-be/src/handlers/mod.rs | Registered proposal_templates handler module |
| nt-be/src/handlers/address_book.rs | Fixed sqlx nullability override for created_by_wallet + regression test |
| nt-be/src/auth/middleware.rs | Added verify_dao_member_for_http helper + tests for policy/cache behavior |
| nt-be/src/auth/error.rs | Added status_and_message helper for consistent HTTP mapping |
| nt-be/migrations/20260619000001_create_proposal_templates.sql | Created proposal_templates table (manifest_id generated column, indexes) |
| nt-be/migrations/20260627000001_add_custom_requests_enabled.sql | Added per-treasury custom_requests_enabled flag |
| nt-be/migrations/20260627000002_add_pinned_to_proposal_templates.sql | Added pinned column to proposal_templates |
| nt-be/.sqlx/query-fd881f5e05e9494b1ca87f66d411ed04133e0d66df5d949b10f5eb78b0676c37.json | sqlx query metadata for proposal template create |
| nt-be/.sqlx/query-f0909d35187099eca523e0e0a0be2547de92116765a206e148e3a80968c975c2.json | sqlx query metadata for proposal template fetch-by-id |
| nt-be/.sqlx/query-e648a256a6492374d9c39511c1ecc63a03f5d653bd018e8a2d90b2951c09081c.json | sqlx query metadata for proposal template update |
| nt-be/.sqlx/query-a999e96f58c9e0f5e0af26d859955ac7c5d65094b52d2944d2551eb735d4861d.json | sqlx query metadata for proposal template list |
| nt-be/.sqlx/query-498093aafc01d2c8e39728c8edb132571cb34e99964883c0bf99eae3fdbe06f6.json | sqlx query metadata for proposal template delete |
| nt-be/.sqlx/query-3abe05be828d94bd877028f9c5c269ec479c5a5b9dc94a1d1cf254236e6b7382.json | sqlx query metadata for setting custom_requests_enabled |
| nt-be/.sqlx/query-0d0919b0bc0e734a5155a2766337c005bf863105549661b4889b74dc6e60c181.json | sqlx query metadata for reading custom_requests_enabled |
| nt-be/.sqlx/query-3eb239014126a32c3fb4793277485fd23afb943625065102af61fd7432bbfb52.json | sqlx query metadata update for address_book list nullability |
| nt-be/.sqlx/query-5f205588489df021222df4247d494a6ad21c0216728876d457954378fcaca1a3.json | sqlx query metadata update for address_book export nullability |
| nt-be/.sqlx/query-36567cc2245450fcf3089d57db17d3c4a7fc68b261d3081a18728b80eb0142a9.json | sqlx query metadata update for address_book list nullability |
| nt-be/.sqlx/query-57e6d33d9168ec4314e8db17f256b14eb9cfee654db2842fe5ac72731d3b0df9.json | Removed stale sqlx query metadata (confidential_intents) |
| docs/CUSTOM_PROPOSAL_TEMPLATES.md | End-user/dev documentation for DSL, UI, permissions, and architecture |
| CLAUDE.md | Added domain knowledge pointer for Custom Proposal Templates |
| .agents/skills/author-custom-proposal-template/SKILL.md | Repo-local agent skill for authoring/fixing manifests aligned to validator |
Files not reviewed (8)
- nt-be/.sqlx/query-0d0919b0bc0e734a5155a2766337c005bf863105549661b4889b74dc6e60c181.json: Generated file
- nt-be/.sqlx/query-3abe05be828d94bd877028f9c5c269ec479c5a5b9dc94a1d1cf254236e6b7382.json: Generated file
- nt-be/.sqlx/query-498093aafc01d2c8e39728c8edb132571cb34e99964883c0bf99eae3fdbe06f6.json: Generated file
- nt-be/.sqlx/query-57e6d33d9168ec4314e8db17f256b14eb9cfee654db2842fe5ac72731d3b0df9.json: Generated file
- nt-be/.sqlx/query-a999e96f58c9e0f5e0af26d859955ac7c5d65094b52d2944d2551eb735d4861d.json: Generated file
- nt-be/.sqlx/query-e648a256a6492374d9c39511c1ecc63a03f5d653bd018e8a2d90b2951c09081c.json: Generated file
- nt-be/.sqlx/query-f0909d35187099eca523e0e0a0be2547de92116765a206e148e3a80968c975c2.json: Generated file
- nt-be/.sqlx/query-fd881f5e05e9494b1ca87f66d411ed04133e0d66df5d949b10f5eb78b0676c37.json: Generated file
Comment on lines
+21
to
+25
| // The Developer tab only does anything for a signed-in member (its Enable action is | ||
| // ChangePolicy-gated), so hide it from guests and signed-out viewers — same idea as the | ||
| // feature-flag-gated Integrations tab. | ||
| const { isGuestTreasury } = useTreasury(); | ||
| const showDeveloper = !isGuestTreasury; |
Comment on lines
+89
to
+95
| <Button | ||
| variant={enabled ? "outline" : "default"} | ||
| disabled={isLoading || setEnabled.isPending} | ||
| onClick={() => toggle(!enabled)} | ||
| > | ||
| {enabled ? "Disable" : "Enable"} | ||
| </Button> |
Comment on lines
+13
to
+16
| This doc is the canonical reference for the manifest DSL, the architecture, and the | ||
| authoring UI. The in-app `/<dao>/custom-templates/about` page and the | ||
| `author-custom-proposal-template` agent skill both derive from it. | ||
|
|
Comment on lines
+184
to
+188
| [`skills/trezu-custom-proposal-template/`](../skills/trezu-custom-proposal-template/SKILL.md) | ||
| installs into Claude Code / Claude.ai / Codex (see its `README`) and produces a manifest | ||
| to paste into the **Code** tab — it needs no repo access. (The `.claude/skills/` skill, | ||
| by contrast, is for agents working *on* the trezu codebase.) | ||
|
|
|
|
||
| - **Subscription System (AI Guide):** [docs/AI_SUBSCRIPTION_GUIDE.md](docs/AI_SUBSCRIPTION_GUIDE.md) - Architecture, key files, payment flows, fee calculations, and implementation details | ||
| - **Pricing Reference:** [docs/PRICING.md](docs/PRICING.md) - Plan tiers, features, and database schema | ||
| - **Custom Proposal Templates:** [docs/CUSTOM_PROPOSAL_TEMPLATES.md](docs/CUSTOM_PROPOSAL_TEMPLATES.md) - the manifest DSL, architecture, args-first authoring, and ChangePolicy gating. An authoring agent skill lives at [.claude/skills/author-custom-proposal-template](.claude/skills/author-custom-proposal-template/SKILL.md) |
- Translate the whole custom-templates feature (customTemplates namespace, 13 locales) instead of leaving it English-only - Tour copy via PageTourContent/pageTours.requestTemplates (was hardcoded JSX) - Sidebar pinned templates use a real <Link> href, not onClick-only - build-proposal docstring: correct the string-substitution vs typed-args contract - Drop the agent/portable skills; the DSL is maintained in docs/
A member-filled value reached the contract as a string: a `{{field}}`
placeholder always stringified, so a bool toggle serialized as
{"approved":"true"} and a number as {"count":"5"} — which serde-based
contracts reject. That made the `bool` and `number` field types effectively
unusable for the methods they imply.
A lone `{{field}}` bound to a bool/number/json field now resolves to its
typed JSON value (true / 5 / parsed object), while u128 uint/amount and any
composed value stay strings. Covered by build-proposal tests.
Also folds in the review fixes made alongside it:
- validate binding.receiver_id (NEAR account) and method_name (identifier)
in the manifest validator and backend validate_manifest
- drop the RegExp lookbehind in the placeholder matcher (Safari <16.4 throws
at module load); handle escaped {{{{...}}}} via alternation instead
- cap the author regex pattern and the input fed to pattern.test (ReDoS)
- localize the Developer tab, the Request Templates sidebar entry, and
member-facing form-validation messages across all 13 locales
- both sidebar navigations use a real <Link> href
- move the distributable skill from skills/ to docs/
- How It Works empty-state button -> outline; tour copy grammar; wrap the
touched-gating SSR test in an intl provider
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.
It is a fork of #993 pulled into the repo to run CI for it.