fix(hola-modern): ux audit + batch 1 remediation + objection lifecycle#49
Merged
Conversation
…eploy tooling Adds the write-side plumbing for encrypted proposal/role content: - useKeyManager / useEncryptedStorage / useRoleFieldContent hooks - 0G storage refactor and hollab-sdk wiring - GovernanceMeetingRoom now emits CreateRoleWithRefs / AmendRoleWithRefs when visibility-scoped content refs are attached Infra: wagmi.config auto-reads Sepolia deployment artifact, Sepolia deploys use --legacy, dev:sepolia helper script, refreshed 11155111 addresses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UX-AUDIT.md adds a severity-ranked frontend audit; this commit addresses 7 findings leaving UX-C-1 (objection lifecycle) as the next focused task. - UX-C-2: drop auto switchChain loop; new WrongNetworkBanner with explicit switch button, shown only on authed views - UX-H-1: live <subname>.hollab.eth preview in create-workspace modal with editable override, 3–32 char validation; deploy hook accepts subname param - UX-M-6: deploy-tx link resolves via chainConfig.blockExplorers instead of hardcoded etherscan.io - UX-M-1: "browse public organizations" secondary CTA on Welcome → #/explore - UX-M-2: delete unreferenced OrganizationOnboarding.tsx - UX-M-3: drop vestigial isOnboarding/onboardedOrgIds state; fresh orgs land on the structure tab with invite panel pre-opened - UX-H-5: migrate MembersView, JoinOrganizationPanel, TacticalMeetingRoom legacy useEffect+setState reads to useQuery per CLAUDE.md convention Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…scard) Closes the top UX-AUDIT finding: the frontend had no write surface for the proposal + objection lifecycle shipped on-chain two weeks ago. Now authenticated members have full lifecycle controls; anyone can clean up expired drafts. - useProposalLifecycle hook: raise / resolve / adopt / discard / discardExpired mutations invalidating the proposal + objection read caches; concern text is keccak-hashed client-side so plaintext stays off-chain - OpenProposalsPanel: per-proposal row in GovernanceView with expiry countdown, open objection list + withdraw/resolve buttons, raise-objection form, admin-gated adopt / discard buttons, permissionless discard-expired - PublicProposalView: renders an "Expired" status badge and a permissionless discard-expired CTA for Draft proposals past the 14-day MAX_PROPOSAL_AGE Admin / facilitator gating is left to the contract (reverts with NotObjectorOrFacilitator / NotOrgAdmin / UnresolvedObjections) — the UI shows all buttons so the state machine is legible. Splitting createProposal from adoptProposal inside GovernanceMeetingRoom remains deferred; the batch path still reverts on out-of-band objections and will be addressed next. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The encrypted-storage commit (6c22eac) pulled @0gfoundation/0g-ts-sdk into the browser bundle, but the SDK's ZgFile class (unused in our code paths — we only touch MemData and ZgBlob) imports node:fs/promises at the top of the module tree. Rollup can't externalize that for the browser, so the build died in the pre-push hook. Fix: switch useZgStorage to the SDK's /browser entry (does the right thing for the types + main API), and alias node:fs/promises to a small shim that throws with a clear message if any browser code path ever reaches it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace manual patterns with OpenZeppelin utilities: - Replace 4 hand-rolled init guards with OZ Initializable - Replace dual-mapping role leads pattern with EnumerableSet.AddressSet - Replace array-based circle/policy tracking with EnumerableSet.UintSet - Replace GovToken's manual minter with OZ Ownable Improve code readability through composition: - Decompose 136-line _applyChange into 16 individual handler functions - Replace 69-line _validateChange with ChangeValidator library - Extract pure validation logic to separate library for testability Fixes: - Fix critical bug in adoptProposal: memory→storage on proposal update (was preventing proposal status from persisting to state) Changes: - ActionVoting.sol: OZ Initializable - MeetingFactory.sol: OZ Initializable + handlers + ChangeValidator - RoleRegistry.sol: EnumerableSet for all multi-value mappings - OrganizationFactory.sol: Update to use transferOwnership() - GovToken.sol: OZ Ownable + backward-compatible minter() view - ChangeValidator.sol: New library for governance change validation - Updated all tests to expect OZ error/event selectors All 187 tests passing. No interface changes. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…nd EnumerableSet Replace manual patterns in RoleDataRegistry: - Manual init guard → OZ Initializable - Array-based item/metric tracking → EnumerableSet.UintSet - Eliminates two O(n) scan-and-pop removal loops (lines 100-107, 149-156) - Removal is now O(1) Changes: - RoleDataRegistry.sol: Initializable + EnumerableSet for _roleChecklists and _roleMetrics - RoleDataRegistry.t.sol: Update to expect Initializable.InvalidInitialization Impact: ~30 lines removed, two storage structures consolidated, O(n)→O(1) removals. All 187 tests passing. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Updates across all packages following contract simplifications: Contracts: - Updated ABIs and generated bindings post-simplification - Deployment scripts updated for new contract signatures Frontend (hola-modern): - Updated hooks and views for new governance flow - Type generation from updated contract bindings Indexing (hollab-indexing): - Schema updates for new RoleDataRegistry integration - Event handler updates for proposal/structural changes SDKs: - agent-sdk: Updated governance module and types - hollab-sdk: Storage schema and OrgClient updates - indexing-client: Type definitions updated - viem-extension: Contract extensions synced Documentation: - Architecture and contract overviews updated - Frontend journey and indexing guides updated Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
MeetingFactory.raiseObjection now takes (proposalId, objectorRoleId, concernHash); the SDK was still calling it with two args, breaking the dts build. Accept optional objectorRoleId on the concern input (defaults to 0n for Facilitator/Secretary). Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…efresh Moves showToast/listener plumbing into components/toastBus.ts so ToastHost.tsx only exports the component, clearing the react-refresh/only-export-components error that was blocking pre-push lint. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
… per-org MAX_PROPOSAL_AGE First of three commits pivoting hollab.eth from faithful-Holacracy-v5 to Holacracy-shaped governance for agent-operated orgs. See specs/99-agent-native-divergence.md (next commit) for the product framing. MeetingFactory changes: - createProposal: _proposerRoleId == 0 is now free (propose as a member). Non-zero still verified via isRoleLead so attribution can't be forged. The objection path remains strictly gated on circle role-leads — the real guardrail on adoption. - MAX_PROPOSAL_AGE constant removed. Replaced with per-org _proposalMaxAge settable by org admin within [MIN=1h, MAX=30d]. DEFAULT=7d (down from 14d) at initialize(). New setProposalMaxAge / proposalMaxAge / ProposalMaxAgeUpdated / MeetingFactory_InvalidProposalMaxAge. Tests: 64/64 in MeetingFactoryProposals.t.sol, 196/196 across the forge suite. Added 9 new cases — imposter-claim guard, zero-roleId happy path, non-member guard, default-is-7d, admin setter roundtrip, out-of-bounds min/max, event emission, and discardExpiredProposal applying the new window. E2E: added 2 scenarios in apps/hola-modern/e2e/journey.spec.ts (zero-roleId proposal + admin-tightened expiry roundtrip). 19/19 pass. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Second of three commits on the agent-native-governance pivot. Captures the four deliberate departures from Holacracy v5.0 in a single source of truth, and reframes the README away from "faithful v5 implementation" to "Holacracy-shaped governance for agent-operated organizations". Strategy: preserve specs/00..07 verbatim as the v5.0 reference (required per CC BY-SA 4.0 attribution) and publish the four divergences in a separate 99- doc — avoids the read-two-docs-to-know-the-truth problem while keeping provenance clean. - specs/99-agent-native-divergence.md (new): representation-as-attribution, per-org proposalMaxAge, meetings-as-optional-wrappers, agent-eligible Facilitator/Secretary — each with the v5.0 quote, the divergence, contract pointer, and rationale. - README.md: Specifications section now frames the suite as Holacracy-shaped, not Holacracy-strict. License/Attribution + Trademark Notice blocks updated to reflect CC BY-SA 4.0 change disclosure and the must-not-market-as-Holacracy constraint. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Third and final commit on the agent-native-governance pivot. Aligns the frontend with the new per-org proposalMaxAge and the optional-meeting framing from specs/99-agent-native-divergence.md. - hooks/useProposalLifecycle.ts: new useProposalMaxAge(meetingFactoryAddress) TanStack-Query hook reads the per-org window from the contract. Delete the hardcoded MAX_PROPOSAL_AGE_SECONDS = 14d constant; replace with DEFAULT_PROPOSAL_MAX_AGE_SECONDS = 7d fallback while the read is in flight. isProposalExpired / secondsUntilExpiry now take maxAgeSeconds explicitly. - views/OpenProposalsPanel.tsx + views/PublicProposalView.tsx: use the hook; 14-day UX copy reframed to "expiry window". - hooks/useGovernanceMeeting.ts: JSDoc reframe — convene/complete are optional reporting wrappers, not prerequisites for governance writes. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
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
audits/UX-AUDIT.md(22 findings, 2 Critical / 5 High / 7 Medium / 5 Low / 3 Info), scoped to the MVP golden path against paperclip-style agent-native posture.<subname>.hollab.ethpreview + override in create modal (UX-H-1), chain-aware explorer link (UX-M-6), "Browse public organizations" CTA on Welcome (UX-M-1), delete deadOrganizationOnboarding.tsx(UX-M-2), drop vestigialisOnboardingscaffolding + land new orgs on the structure tab with invite open (UX-M-3), migrate three legacy hooks touseQuery(UX-H-5).useProposalLifecyclehook with raise / resolve / adopt / discard / discardExpired mutations; newOpenProposalsPanelinGovernanceViewwith expiry countdowns, objection list + withdraw/resolve buttons, raise-objection form (concern text hashed client-side), admin adopt/discard, permissionless discard-expired;PublicProposalViewgains an "Expired" badge + permissionless discard CTA past the 14-day window.CreateRoleWithRefs/AmendRoleWithRefs) + Sepolia deploy tooling (wagmi auto-discovery,--legacyflag,dev:sepoliascript, refreshed 11155111 addresses).node:fs/promisesto a throw-on-call shim so 0g-ts-sdk'sZgFile(unused in browser code paths) can resolve without breaking the Vite build.Still deferred
Splitting
createProposalfromadoptProposalinsideGovernanceMeetingRoom.tsx— the batched meeting-completion still reverts on out-of-band objections.OpenProposalsPanelis the interim path until that lands.Test plan
pnpm check-typesclean (verified locally; also runs in pre-push hook)pnpm --filter hola-modern buildgreen (verified; passed pre-push)pnpm --filter hola-modern testunit suite (47 tests) passes<subname>.hollab.ethpreview appears under the org-name input and is editableRaise objectioninOpenProposalsPanel, confirm it lands in the indexer and the objection appears with a resolve buttonWithdraw— confirm the objection clears andAdoptre-enablesAdopt— confirm tx lands and row flips out of the open listPublicProposalViewshows "Expired" + the permissionless discard CTA🤖 Generated with Claude Code