Skip to content

fix(hola-modern): ux audit + batch 1 remediation + objection lifecycle#49

Merged
Skanislav merged 12 commits into
devfrom
fix/audit-remediations-and-optimizations
Apr 21, 2026
Merged

fix(hola-modern): ux audit + batch 1 remediation + objection lifecycle#49
Skanislav merged 12 commits into
devfrom
fix/audit-remediations-and-optimizations

Conversation

@Skanislav

Copy link
Copy Markdown
Contributor

Summary

  • Audit: new 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.
  • Batch 1 remediation (7 findings): wrong-network banner instead of auto-switch loop (UX-C-2), live <subname>.hollab.eth preview + override in create modal (UX-H-1), chain-aware explorer link (UX-M-6), "Browse public organizations" CTA on Welcome (UX-M-1), delete dead OrganizationOnboarding.tsx (UX-M-2), drop vestigial isOnboarding scaffolding + land new orgs on the structure tab with invite open (UX-M-3), migrate three legacy hooks to useQuery (UX-H-5).
  • Objection lifecycle (UX-C-1): new useProposalLifecycle hook with raise / resolve / adopt / discard / discardExpired mutations; new OpenProposalsPanel in GovernanceView with expiry countdowns, objection list + withdraw/resolve buttons, raise-objection form (concern text hashed client-side), admin adopt/discard, permissionless discard-expired; PublicProposalView gains an "Expired" badge + permissionless discard CTA past the 14-day window.
  • Also ships the encryption plumbing that was sitting uncommitted (useKeyManager / useEncryptedStorage / useRoleFieldContent, 0G storage refactor, GovernanceMeetingRoom now emits CreateRoleWithRefs / AmendRoleWithRefs) + Sepolia deploy tooling (wagmi auto-discovery, --legacy flag, dev:sepolia script, refreshed 11155111 addresses).
  • Build fix: aliased node:fs/promises to a throw-on-call shim so 0g-ts-sdk's ZgFile (unused in browser code paths) can resolve without breaking the Vite build.

Still deferred

Splitting createProposal from adoptProposal inside GovernanceMeetingRoom.tsx — the batched meeting-completion still reverts on out-of-band objections. OpenProposalsPanel is the interim path until that lands.

Test plan

  • pnpm check-types clean (verified locally; also runs in pre-push hook)
  • pnpm --filter hola-modern build green (verified; passed pre-push)
  • pnpm --filter hola-modern test unit suite (47 tests) passes
  • Visual: create a workspace, confirm <subname>.hollab.eth preview appears under the org-name input and is editable
  • Visual: connect on a wrong chain, confirm amber "Switch to …" banner appears instead of a wallet prompt loop
  • Visual: open a Draft proposal under a fresh meeting, use Raise objection in OpenProposalsPanel, confirm it lands in the indexer and the objection appears with a resolve button
  • Visual: as the objector, click Withdraw — confirm the objection clears and Adopt re-enables
  • Visual: as admin, click Adopt — confirm tx lands and row flips out of the open list
  • Visual: time-travel / mock an old proposal (or wait 14d in testnet), confirm PublicProposalView shows "Expired" + the permissionless discard CTA

🤖 Generated with Claude Code

Skanislav and others added 12 commits April 16, 2026 21:56
…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>
@Skanislav Skanislav merged commit 7fb57d5 into dev Apr 21, 2026
6 checks passed
@Skanislav Skanislav deleted the fix/audit-remediations-and-optimizations branch April 21, 2026 12:33
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.

1 participant