Skip to content

feat(ux): redis-backed pending state + multi-block dashboard + short toast (Phase 2c demo polish)#49

Merged
ComBba merged 2 commits into
mainfrom
feat/compose-redis-pending-state
May 14, 2026
Merged

feat(ux): redis-backed pending state + multi-block dashboard + short toast (Phase 2c demo polish)#49
ComBba merged 2 commits into
mainfrom
feat/compose-redis-pending-state

Conversation

@ComBba
Copy link
Copy Markdown
Contributor

@ComBba ComBba commented May 14, 2026

Summary

Three demo-recording UX wins driven by user feedback on the Phase 1.7b take.

Symptom in the recording Fix
Confirm modal had 7 disabled `(internal)` carrier fields, scroll-only Stash compile state in Redis, ship the modal one short pendingId
Dashboard description collapsed into a wall of text Split into multiple paragraph fields per section
Success toast ~150 chars, truncated mid-sentence Shortened to `Saved "X". Dry-run starts now.` (~50 chars)

Changes

`src/server/routes/compose.ts`

  • New helper `newPendingId()` — 12 hex chars from `crypto.getRandomValues`.
  • compose-rule-submit now writes the validated rule + tokens + model to `keys.composePending(sub, pendingId)` (10-min TTL) and the modal carries only the pendingId.
  • compose-confirm-submit reads + deletes the pending entry. Both Save and Edit branches handle missing/expired pending with an actionable toast.
  • `persistRuleAndStartDryRun()` toast shortened.

`src/server/routes/dashboard.ts`

  • Replaced the one-big-string `description` with per-section disabled-paragraph fields. Each section (rpc warning / welcome / empty state / counts / token cost / dry-run preview / recent actions) is its own block, rendered with proper spacing in Devvit's modal.

`src/shared/redis-keys.ts`

  • Added `keys.composePending(sub, id)`.

Tests

  • routes-compose.test.ts: split the save-flow test into compose → assert pending → save → assert consumed. New assertions: pendingId is the only visible carrier, Redis entry has the right model, entry is deleted post-save. Toast assertions updated.
  • routes-dashboard.test.ts: `dashTexts(body)` helper concatenates labels + paragraph values; tests assert against that, not the single-description string.

`scripts/chrome-reddit-verify-phase17b.py`

  • New env vars: `FULLSCREEN=1` (start-maximized + no_viewport for clean OBS recording), `SLOW_MO=600` (per-action delay).
  • Multi-round clarify loop (handles up to 3 LLM follow-ups before bailing).
  • Robust option picker (tries native ``, then `faceplate-radio-input` / `faceplate-listbox-item` / `[role=option]`). Field-name signal detection for clarify / confirm / dashboard / manage forms. Test plan [x] `npm run check` 4/4 gates green (211 unit/integration tests) [ ] `npx devvit upload` → re-run verify script in fullscreen for the new recording 🤖 Generated with Claude Code Summary by CodeRabbit 릴리스 노트 새로운 기능 규칙 작성 확인 모달 재설계로 더 간결하고 명확한 정보 표시 대시보드 레이아웃을 구조화된 정보 블록으로 개편하여 가독성 향상 버그 수정 규칙 작성 세션 만료 시 사용자 안내 메시지 개선 토스트 메시지 길이 단축으로 완전한 문장 표시 보장 테스트 규칙 작성 및 확인 흐름 테스트 업데이트 대시보드 UI 다중 블록 레이아웃 테스트 강화

…+ short toast

Three demo-recording UX wins driven by user feedback on the Phase 1.7b
recording — the confirm/clarify modals were too long to fit on screen,
the dashboard description collapsed to an unreadable wall of text, and
the success toast got truncated mid-sentence.

## 1. Compose-confirm carries one short pendingId, not 7 internal carriers

Before: the composeConfirmForm shipped 7 disabled `(internal)` fields
across the form (rule / allowGuarded / serializedRule / tokensIn /
tokensOut / llmModel / usingBYOK). The modal was so tall the moderator
had to scroll past a wall of "(internal) ..." labels to see the
compiled summary or the Save button.

After: compose-rule-submit stashes the entire compile state under
`keys.composePending(sub, pendingId)` (10-min TTL, JSON), then the
form carries only the 12-char hex pendingId. compose-confirm-submit
reads + deletes the entry. Three visible fields total: compiledSummary,
editInsteadOfSave, pendingId — fits on one screen.

New helper `newPendingId()` (crypto.getRandomValues, 12 hex chars).
New Redis key `keys.composePending(sub, id)`.

## 2. Dashboard splits its summary into multiple disabled-paragraph blocks

Before: every dashboard line was joined with `\n` into one big
`description` string, which Devvit's modal collapses into a single
soft-wrapping paragraph. The recording showed everything (welcome
card, counts, token cost, dry-run preview, recent actions) as one
unreadable run-on sentence.

After: each section is a separate `paragraph` field with its own
`label`. Devvit renders these as visually distinct blocks with the
label as a sub-heading, so the moderator can actually read the
sections at a glance.

## 3. Success toast is short

Before: `Compiled rule "X". → post: modqueue. Dry-run started — open
the subreddit ⋯ menu → "vibe-mod: View rules + log" to see preview.`
~150 chars — Devvit toasts truncated it mid-sentence.

After: `Saved "X". Dry-run starts now.` < 60 chars. The compose-confirm
form already shows the rule name + cost + humanizeRule output, so the
toast just needs to confirm "this happened".

## Verification
- `npm run check` 4/4 gates green (211 unit/integration tests)
- Updated routes-compose.test.ts: pending-entry round-trip + short-toast assertions
- Updated routes-dashboard.test.ts: text-helper that concatenates field
  labels+values (replaces the old single-`description` string scan)
- Verify script (chrome-reddit-verify-phase17b.py): added FULLSCREEN +
  SLOW_MO env vars + multi-round clarify loop + better select picker.

Refs: docs/demo-scenario.md (recording target)
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

Warning

Rate limit exceeded

@ComBba has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 9 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2cce5aca-4616-4558-a1a1-405628f9ec28

📥 Commits

Reviewing files that changed from the base of the PR and between 7208695 and a28c3b8.

📒 Files selected for processing (4)
  • scripts/chrome-reddit-verify-phase17b.py
  • src/server/routes/compose.ts
  • src/server/routes/dashboard.ts
  • test/devvit-testkit.ts

개요

Compose 규칙 제출 흐름을 Redis 임시 스테이징으로 재구성하여 검증된 데이터를 pendingId로 단기 저장하고, 대시보드 UI를 구조화된 다중 블록 레이아웃으로 변경합니다. 브라우저 검증 스크립트에 전체 화면/슬로우 모션 옵션과 다중 라운드 명확화 루프를 추가합니다.

변경 사항

Compose 두 단계 레디스 스테이징 흐름

레이어 / 파일 요약
레디스 composePending 키 헬퍼
src/shared/redis-keys.ts
Compose 흐름의 임시 상태용 Redis 키 빌더를 추가합니다. 검증된 규칙 아티팩트와 토큰/모델 메타데이터를 10분 TTL로 저장합니다.
Compose 라우트 스테이징 및 확인 로직
src/server/routes/compose.ts
compose-rule-submit은 검증 후 메타데이터를 Redis에 저장하고 pendingId를 반환합니다. compose-confirm-submit은 pendingId로 Redis에서 데이터를 로드하여 편집 시 pending을 삭제하고 폼을 다시 열며, 저장 시 재검증 후 규칙을 지속합니다.
Compose 두 단계 흐름 테스트
src/server/routes-compose.test.ts
성공 토스트와 두 단계 흐름을 테스트합니다. confirm form의 pendingId와 compiledSummary를 검증하고, Redis pending 상태를 확인하고, pending 항목이 저장 후 소비되는지 확인합니다.

대시보드 다중 블록 레이아웃

레이어 / 파일 요약
대시보드 라우트 다중 블록 빌더
src/server/routes/dashboard.ts
대시보드 폼을 단일 description에서 addBlock 기반 구조화된 FormField 배열로 변경합니다. RPC 경고, 온보딩 카드, 빈 상태, 카운트, 토큰 비용, 드라이런 미리보기, 최근 작업을 조건부로 추가합니다.
대시보드 테스트 dashTexts 헬퍼
src/server/routes-dashboard.test.ts
dashTexts(body) 헬퍼를 추가하여 다중 블록 레이아웃을 어설션합니다. 활성/드래프트 규칙, 최근 작업, 토큰 비용, 드라이런 미리보기, 온보딩, 빈 상태 텍스트를 검증합니다.

브라우저 테스트 다중 라운드 명확화

레이어 / 파일 요약
환경 구성 상수
scripts/chrome-reddit-verify-phase17b.py
FULLSCREEN과 SLOW_MO 환경 변수를 추가합니다. 전체 화면 브라우저 녹화와 Playwright 액션 간 슬로우 모션을 제어합니다.
Playwright 초기화
scripts/chrome-reddit-verify-phase17b.py
FULLSCREEN 활성화 시 --start-maximized와 no_viewport를 추가합니다. SLOW_MO를 launch 옵션으로 전달합니다.
다중 라운드 명확화 루프
scripts/chrome-reddit-verify-phase17b.py
명확화 모달이 존재하는 동안 picker 루프를 3회로 제한합니다. 각 라운드마다 라운드별 단계 이름을 기록하고, Re-compile을 제출하고, 폼을 다시 덤프합니다.

예상 코드 리뷰 노력

🎯 3 (보통) | ⏱️ ~25분

관련 PR

  • Two-Weeks-Team/vibe-mod#44: 두 PR 모두 composeConfirmForm/compose-confirm-submit 주변의 compose 흐름을 업데이트하여 검증된 컴파일을 Redis에 스테이징하고(pendingId를 통해) 서버 측 명확화 라운드 처리를 추가합니다.
  • Two-Weeks-Team/vibe-mod#30: 두 PR 모두 /internal/form/compose-rule-submit compose/compile 흐름을 수정하여 플러그인 RPC/OpenAI 조회 실패를 처리합니다(메인 PR은 Redis의 pendingId를 통해 상태를 스테이징하도록 리팩토링; 검색된 PR은 try/catch 보호와 폴백 포커스 테스트로 강화합니다).

🐰 Compose flows now stage in Redis bright,
Two-step journeys take their rightful flight,
Dashboard blocks and browser rounds align,
Multi-pass clarifications—surely fine! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: Redis-backed pending state for compose flow, multi-block dashboard layout, and shortened toast messages for Phase 2c UX improvements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/compose-redis-pending-state

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the rule composition and dashboard forms to improve the user experience within Devvit's UI constraints. Key changes include moving internal compilation state to Redis to declutter modals, shortening success toasts to prevent truncation, and splitting the dashboard summary into multiple paragraph blocks for better readability. Additionally, the verification script was updated to handle multi-round LLM clarifications and new execution flags like FULLSCREEN and SLOW_MO. Feedback was provided regarding a misleading 'cancel' label on the dashboard onboarding form that fails to persist the dismissal due to platform limitations.

Comment thread src/server/routes/dashboard.ts Outdated
description: summary,
description: 'Read-only summary. Per-rule activation lives in "vibe-mod: Manage rules".',
acceptLabel: 'Close',
cancelLabel: firstVisit ? "Don't show intro again" : 'Cancel',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The cancelLabel is set to "Don't show intro again" when firstVisit is true. However, in Devvit, clicking the cancel button on a form does not trigger the form's submit handler. This means that clicking this button will close the modal but will NOT persist the dismissal to Redis (the dashboard-action route is not called). Consequently, the onboarding intro will reappear on the next visit, making the button label misleading.

To fix this, you should either remove the misleading label or rely solely on the dismissOnboarding checkbox and the "Close" (accept) button for persistence.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/server/routes-compose.test.ts (1)

438-452: ⚡ Quick win

테스트에서도 composePending 키 헬퍼를 사용해 키 계약 드리프트를 막으세요.

현재 하드코딩 문자열(testsub:compose:pending:...)은 키 포맷 변경 시 테스트가 실제 계약과 어긋날 수 있습니다.

♻️ 제안 수정안
 import {
   fakeRedis,
   fakeReddit,
   fakeSettings,
   fakeScheduler,
   fakeFetch,
   fakeListing,
   openaiResponse,
   openaiError,
 } from '../../test/setup';
+import { keys } from '../shared/redis-keys';
@@
-const pendingJson = JSON.parse((await fakeRedis.get(`testsub:compose:pending:${fieldsByName.pendingId}`))!);
+const pendingKey = keys.composePending('testsub', fieldsByName.pendingId as string);
+const pendingJson = JSON.parse((await fakeRedis.get(pendingKey))!);
@@
-expect(await fakeRedis.get(`testsub:compose:pending:${fieldsByName.pendingId}`)).toBeUndefined();
+expect(await fakeRedis.get(pendingKey)).toBeUndefined();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/routes-compose.test.ts` around lines 438 - 452, Replace the
hardcoded Redis key string `testsub:compose:pending:${fieldsByName.pendingId}`
with the project's key helper (e.g., composePending(...)) to avoid contract
drift; update both reads/expectations that call fakeRedis.get(...) to use
composePending with the pending id (fieldsByName.pendingId) and ensure any
necessary import of composePending is added at the top of
src/server/routes-compose.test.ts so fakeRedis.get(composePending(...)) and the
final expect compare against the helper-built key.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/chrome-reddit-verify-phase17b.py`:
- Line 43: The SLOW_MO environment parsing uses int(os.environ.get("SLOW_MO",
"0")) which will raise ValueError for non-numeric values; change it to a safe
parse that falls back to 0 (or a defined default) on any parsing error or
invalid input—e.g., read os.environ.get("SLOW_MO"), attempt to int() in a
try/except (or validate with str.isdigit()/regex) and set SLOW_MO to the default
on exception, so the script won’t crash when SLOW_MO contains invalid text.

In `@src/server/routes/compose.ts`:
- Around line 502-511: The daily quota increment currently lives inside
persistRuleAndStartDryRun (used on save), which lets users repeatedly trigger
compile operations without increasing the count; move the quota counting to
immediately after a successful compile/submit operation instead: remove the
increment logic from persistRuleAndStartDryRun and add the quota increment call
in the compile-submit flow (the handler/function that performs the compile and
calls OpenAI) so a successful compile immediately increments
todayCount/todayCounterKey. Ensure you reference and update the symbols
persistRuleAndStartDryRun and the compile-submit handler (the function that
invokes the compiler/OpenAI in this route) and preserve existing error handling
around the compile before incrementing.
- Around line 421-423: The read-and-consume of a pending item is not atomic:
code that calls redis.get(keys.composePending(subredditName, pendingId)) (where
pendingJson gets parsed into ComposePending) can be raced by concurrent requests
and later code that deletes or handles the pending (the block that previously
used pendingJson and later removed keys) may run twice; replace the non-atomic
get + later delete with an atomic GETDEL (or equivalent Redis EVAL/Lua
transaction) when fetching the pending entry so the read-and-consume happens
once—use redis GETDEL (or redis.sendCommand('GETDEL', ...)) or a Lua script to
return and remove keys.composePending(subredditName, pendingId) in one atomic
operation to guarantee single consumption and prevent duplicate
save/count/schedule flows.
- Around line 327-333: After calling redis.set for
keys.composePending(subredditName, pendingId) you must detect whether the set
succeeded and, if redis.expire(keys.composePending(...), 600) fails, immediately
roll back by deleting the key; update the try/catch so that after await
redis.set(...) you treat that as a successful write and wrap the subsequent
await redis.expire(...) in its own failure-handling path that calls await
redis.del(keys.composePending(subredditName, pendingId)) on expire error
(include any logged error), ensuring no TTL-less pending entries remain for
pendingId/subredditName.

---

Nitpick comments:
In `@src/server/routes-compose.test.ts`:
- Around line 438-452: Replace the hardcoded Redis key string
`testsub:compose:pending:${fieldsByName.pendingId}` with the project's key
helper (e.g., composePending(...)) to avoid contract drift; update both
reads/expectations that call fakeRedis.get(...) to use composePending with the
pending id (fieldsByName.pendingId) and ensure any necessary import of
composePending is added at the top of src/server/routes-compose.test.ts so
fakeRedis.get(composePending(...)) and the final expect compare against the
helper-built key.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c79add6-5f31-45d4-8cfb-c7ef5e7673d3

📥 Commits

Reviewing files that changed from the base of the PR and between 92ff299 and 7208695.

📒 Files selected for processing (6)
  • scripts/chrome-reddit-verify-phase17b.py
  • src/server/routes-compose.test.ts
  • src/server/routes-dashboard.test.ts
  • src/server/routes/compose.ts
  • src/server/routes/dashboard.ts
  • src/shared/redis-keys.ts

Comment thread scripts/chrome-reddit-verify-phase17b.py Outdated
Comment thread src/server/routes/compose.ts
Comment thread src/server/routes/compose.ts Outdated
Comment thread src/server/routes/compose.ts
5 review issues from Gemini code-assist + CodeRabbit on PR #49.

## Major

### CodeRabbit #4 — pending consumption now atomic (WATCH/MULTI/DEL/EXEC)
Two concurrent submits with the same `pendingId` (back-button + re-submit,
double-click on Save, multi-tab moderator) could both read the entry
before either deleted it, doubling the bundle write + dry-run schedule.
Replaced the plain GET-then-DEL with a WATCH/MULTI/DEL/EXEC round-trip.
Whichever caller commits first wins; the loser sees `exec() == null` and
gets an actionable "Another submission consumed this confirmation" toast.

Updated FakeTxn to match (added `del()` method on the txn object — the
production Devvit Redis client supports it, the fake didn't).

### CodeRabbit #5 — daily compile counter now bumps at compile time
Quota was previously incremented inside `persistRuleAndStartDryRun()`,
which only runs on Save. A moderator could repeatedly hit Compile +
Cancel and burn through OpenAI tokens without the per-day counter ever
moving. Moved the increment to immediately after a successful OpenAI
return in compose-rule-submit. The token cost is real either way, so
the quota now reflects it either way.

## Medium

### Gemini #1 — dashboard cancelLabel no longer says "Don't show intro again"
Devvit's Cancel button does NOT trigger the form submit handler, so
clicking the misnamed button could not actually persist
`dismissOnboarding` — it just closed the modal. Reverted the label to
"Cancel"; the dismissOnboarding boolean toggle + Close (acceptLabel) is
the real opt-out path that submits the form values.

## Minor

### CodeRabbit #2 — verify script tolerates non-int SLOW_MO
`int(os.environ.get("SLOW_MO", "0"))` crashed if a user passed e.g.
`SLOW_MO=fast`. Added `_safe_int` helper that warns + falls back to the
default rather than ending the recording session before it starts.

### CodeRabbit #3 — atomic SET + TTL via the `expiration` option
Previous shape was `redis.set(key, value)` then `redis.expire(key, ttl)`,
which could leak a TTL-less pending key if expire failed after set
succeeded. Switched to `redis.set(key, value, { expiration: new Date(...) })`
which is the same single round-trip the rollback writer in executor.ts
already uses.

## Verification
- `npm run check` 4/4 gates green (211 unit/integration tests)
- All compose tests still pass with the new pending-id flow
- Race condition smoke: a second compose-confirm-submit call against the
  same pendingId now returns the "another submission consumed" toast
  instead of double-persisting (previously: double bundle write).
@ComBba ComBba merged commit 0f25c1f into main May 14, 2026
2 checks passed
@ComBba ComBba deleted the feat/compose-redis-pending-state branch May 14, 2026 07:41
ComBba pushed a commit that referenced this pull request May 15, 2026
5 review issues from Gemini code-assist + CodeRabbit on PR #49.

## Major

### CodeRabbit #4 — pending consumption now atomic (WATCH/MULTI/DEL/EXEC)
Two concurrent submits with the same `pendingId` (back-button + re-submit,
double-click on Save, multi-tab moderator) could both read the entry
before either deleted it, doubling the bundle write + dry-run schedule.
Replaced the plain GET-then-DEL with a WATCH/MULTI/DEL/EXEC round-trip.
Whichever caller commits first wins; the loser sees `exec() == null` and
gets an actionable "Another submission consumed this confirmation" toast.

Updated FakeTxn to match (added `del()` method on the txn object — the
production Devvit Redis client supports it, the fake didn't).

### CodeRabbit #5 — daily compile counter now bumps at compile time
Quota was previously incremented inside `persistRuleAndStartDryRun()`,
which only runs on Save. A moderator could repeatedly hit Compile +
Cancel and burn through OpenAI tokens without the per-day counter ever
moving. Moved the increment to immediately after a successful OpenAI
return in compose-rule-submit. The token cost is real either way, so
the quota now reflects it either way.

## Medium

### Gemini #1 — dashboard cancelLabel no longer says "Don't show intro again"
Devvit's Cancel button does NOT trigger the form submit handler, so
clicking the misnamed button could not actually persist
`dismissOnboarding` — it just closed the modal. Reverted the label to
"Cancel"; the dismissOnboarding boolean toggle + Close (acceptLabel) is
the real opt-out path that submits the form values.

## Minor

### CodeRabbit #2 — verify script tolerates non-int SLOW_MO
`int(os.environ.get("SLOW_MO", "0"))` crashed if a user passed e.g.
`SLOW_MO=fast`. Added `_safe_int` helper that warns + falls back to the
default rather than ending the recording session before it starts.

### CodeRabbit #3 — atomic SET + TTL via the `expiration` option
Previous shape was `redis.set(key, value)` then `redis.expire(key, ttl)`,
which could leak a TTL-less pending key if expire failed after set
succeeded. Switched to `redis.set(key, value, { expiration: new Date(...) })`
which is the same single round-trip the rollback writer in executor.ts
already uses.

## Verification
- `npm run check` 4/4 gates green (211 unit/integration tests)
- All compose tests still pass with the new pending-id flow
- Race condition smoke: a second compose-confirm-submit call against the
  same pendingId now returns the "another submission consumed" toast
  instead of double-persisting (previously: double bundle write).
ComBba added a commit that referenced this pull request May 15, 2026
…-state

feat(ux): redis-backed pending state + multi-block dashboard + short toast (Phase 2c demo polish)
ComBba pushed a commit that referenced this pull request May 15, 2026
…ock dashboard, short toast)

Three test-side updates after PR #49 reshaped the modals:

1. **confirm-form-renders** now checks for `pendingId + editInsteadOfSave +
   compiledSummary` (was: `serializedRule + compiledSummary` — the former
   field is gone, the rule lives in Redis under composePending).

2. **dashboard-onboarding / token-cost** now scans every field label +
   defaultValue on top of dialogText. Phase 2c split the dashboard's
   single description into per-section paragraph fields, so the keywords
   ("Welcome to vibe-mod", "Tokens used", "gpt-5") moved into field
   labels/values.

3. **save-toast-success** now matches the new short toast shape
   (`Saved "X". Dry-run starts now.`) instead of the old
   `Compiled rule "X". → trigger: action. Dry-run started — open menu...`.

Verified against r/SocialSeeding v0.0.46 — 16/16 step PASS, including a
two-round clarify (the script's multi-round loop kicks in when the LLM
needs another disambiguation pass).
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