Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cursor/skills/make-plans/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ description: Structures plan.md files for parallel, non-overlapping agent work.

Plans enable **parallel, non-overlapping work**. Each task must be independent; agents work in a **dedicated git worktree** (see worktrees skill).

**Where to keep plan.md and spec files:** Create and edit plan.md (and the other Ralph spec files: goal.md/spec.md, state.json, decisions.md) in a **dedicated folder** under the **specs** folder at the **repo root**: `specs/<feature-or-component-name>/`. Example: `specs/resizable-container/plan.md`. Do not place spec files next to source (e.g. not under `src/components/...`).

## Required plan.md Structure

1. **Worktree section** (top) — path, branch, base branch. See worktrees skill.
Expand Down
2 changes: 2 additions & 0 deletions .cursor/skills/make-plans/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Source: `.ai/MAKE_PLAN.md`. Condensed in SKILL.md; details here.

**Spec files location:** Create plan.md (and other Ralph spec files) under `specs/<feature-or-component-name>/` at repo root.

## Execution Order Example

```md
Expand Down
24 changes: 13 additions & 11 deletions .cursor/skills/ralph-protocol/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: ralph-protocol
description: Collaboration protocol for Ralph loop (Plan → Act → Reflect → Refine). Use when working from goal.md, plan.md, state.json, decisions.md; when executing tasks in a shared plan; or when the user mentions Ralph, multi-agent, or file-based collaboration.
description: Collaboration protocol for Ralph loop (Plan → Act → Reflect → Refine). Use when working from spec files in specs/<name>/ (goal.md/spec.md, plan.md, state.json, decisions.md); when executing tasks in a shared plan; or when the user mentions Ralph, multi-agent, or file-based collaboration.
---

# Ralph Protocol (Agent Collaboration)
Expand All @@ -9,27 +9,29 @@ Files are the source of truth. All agents share memory via files. No silent deci

## Required Files

| File | Purpose |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **goal.md** | What we achieve; success criteria; constraints; non-goals. Read first. Only change if goal actually changes. No implementation details. |
| **plan.md** | How we achieve it. Ordered tasks, ownership, dependencies, status (`pending \| in-progress \| done \| blocked`). Propose changes before big deviations; don't rewrite completed sections. |
| **state.json** | Current memory. Task statuses, flags (`blocked`, `needs-review`, etc.). Update immediately after acting. Read before assuming anything. |
| **decisions.md** | Log of non-trivial decisions (what + why). Append only; never delete. Prevents reopening or contradicting past choices. |
**Location:** Generate and keep spec files in a **dedicated folder** inside the **specs** folder at the **root of the repo**: `specs/<feature-or-component-name>/`. Example: `specs/resizable-container/goal.md`, `specs/resizable-container/plan.md`, `specs/resizable-container/state.json`, `specs/resizable-container/decisions.md`. One folder per feature or component; do not put spec files next to source (e.g. not under `src/components/...`).

| File | Purpose |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **spec.md** or **goal.md** | What we achieve; success criteria; constraints; non-goals. Read first. Only change if goal actually changes. No implementation details. |
| **plan.md** | How we achieve it. Ordered tasks, ownership, dependencies, status (`pending \| in-progress \| done \| blocked`). Propose changes before big deviations; don't rewrite completed sections. |
| **state.json** | Current memory. Task statuses, flags (`blocked`, `needs-review`, etc.). Update immediately after acting. Read before assuming anything. |
| **decisions.md** | Log of non-trivial decisions (what + why). Append only; never delete. Prevents reopening or contradicting past choices. |

## Workflow

**Before acting:** Read goal.md → plan.md → state.json → decisions.md.
**Before acting:** Read the spec files in `specs/<name>/`: spec.md (or goal.md) → plan.md → state.json → decisions.md.

**During:** Follow the plan; no overlapping work unless coordinated; no undocumented decisions.

**After:** Update state.json → record decisions in decisions.md → update task status in plan.md. Optionally add learnings to observations.md.
**After:** Update `specs/<name>/state.json` → record decisions in `specs/<name>/decisions.md` → update task status in `specs/<name>/plan.md`. Optionally add learnings to observations.md in the same folder.

**Prohibited:** Decisions without recording; using chat as memory; re-solving done problems; changing goals implicitly; overwriting files without explanation.

## Task ownership (critical)

- Work on **exactly one** task at a time.
- That task must be in plan.md, marked `in-progress` and assigned to you.
- That task must be in the plan file (`specs/<name>/plan.md`), marked `in-progress` and assigned to you.
- Do not change files for other tasks, even if small.

## Commit scope
Expand All @@ -42,6 +44,6 @@ When acceptance criteria involve UI: use Playwright (MCP or project config). Tak

## Loop reminder

Each iteration: Plan (update plan.md if needed) → Act (scoped work) → Reflect (learnings) → Refine (plan or decisions).
Each iteration: Plan (update `specs/<name>/plan.md` if needed) → Act (scoped work) → Reflect (learnings) → Refine (plan or decisions).

For decision log format and state.json example, see [reference.md](reference.md). Plan structure and worktrees: use make-plans and worktrees skills.
2 changes: 2 additions & 0 deletions .cursor/skills/ralph-protocol/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Source: `.ai/RALPH.md`

**Spec files location:** Keep all spec files (spec.md, plan.md, state.json, decisions.md) in a dedicated folder at repo root: `specs/<feature-or-component-name>/`.

## state.json example

```json
Expand Down
4 changes: 2 additions & 2 deletions .cursor/skills/worktrees/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ git worktree add ../stream-chat-react-worktrees/gallery-redesign -b feat/gallery
- **Branch:** `feat/<descriptive-name>` (repo conventions)
- **Base:** current branch when creating

Then in plan.md include a **Worktree** section with path, branch, base branch. Agent must `cd` into the worktree before any work.
Then in the plan file (`specs/<plan-name>/plan.md`) include a **Worktree** section with path, branch, base branch. Agent must `cd` into the worktree before any work.

```bash
cd ../stream-chat-react-worktrees/<plan-name>
Expand Down Expand Up @@ -69,7 +69,7 @@ git push origin agent/<branch-name>
```

- Do this after each meaningful milestone or when someone needs to preview.
- Document in plan.md: **Preview branch:** `agent/<branch-name>` — checkout to preview.
- Document in `specs/<plan-name>/plan.md`: **Preview branch:** `agent/<branch-name>` — checkout to preview.

## Lifecycle

Expand Down
2 changes: 1 addition & 1 deletion examples/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --port 5175",
"build": "tsc && vite build",
"preview": "vite preview"
},
Expand Down
11 changes: 11 additions & 0 deletions examples/vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import {
Chat,
ChatView,
ReactionsList,
MessageInput,
type NotificationListProps,
NotificationList,
WithComponents,
defaultReactionOptions,
type ReactionOptions,
Expand Down Expand Up @@ -129,6 +132,12 @@ const EmojiPickerWithCustomOptions = (
return <EmojiPicker {...props} pickerProps={{ theme: mode }} />;
};

const ConfigurableNotificationList = (props: NotificationListProps) => {
const { verticalAlignment } = useAppSettingsSelector((state) => state.notifications);

return <NotificationList {...props} verticalAlignment={verticalAlignment} />;
};

const App = () => {
const { tokenProvider, userId } = useUser();
const chatView = useAppSettingsSelector((state) => state.chatView);
Expand Down Expand Up @@ -245,6 +254,8 @@ const App = () => {
overrides={{
emojiSearchIndex: SearchIndex,
EmojiPicker: EmojiPickerWithCustomOptions,
MessageListNotifications: ConfigurableNotificationList,
NotificationList: ConfigurableNotificationList,
ReactionsList: CustomMessageReactions,
reactionOptions: newReactionOptions,
Search: CustomChannelSearch,
Expand Down
116 changes: 116 additions & 0 deletions examples/vite/src/AppSettings/ActionsMenu/ActionsMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { useMemo, useState } from 'react';
import type { ComponentProps } from 'react';
import {
Button,
ContextMenu,
ContextMenuButton,
DialogManagerProvider,
IconThunder,
useDialogIsOpen,
useDialogOnNearestManager,
type ContextMenuItemComponent,
} from 'stream-chat-react';
import {
NotificationPromptDialog,
notificationPromptDialogId,
} from './NotificationPromptDialog';

const actionsMenuDialogId = 'app-actions-menu';

const ActionsMenuButton = ({
iconOnly,
isOpen,
onClick,
refCallback,
}: {
iconOnly: boolean;
isOpen: boolean;
onClick: ComponentProps<'button'>['onClick'];
refCallback: (element: HTMLButtonElement | null) => void;
}) => (
<div className='str-chat__chat-view__selector-button-container'>
<Button
appearance='ghost'
aria-expanded={isOpen}
aria-haspopup='true'
aria-label='Open actions'
className='str-chat__chat-view__selector-button app__settings-group_button'
onClick={onClick}
ref={refCallback}
variant='secondary'
>
<IconThunder />
{!iconOnly && (
<div className='str-chat__chat-view__selector-button-text'>Actions</div>
)}
</Button>
{iconOnly && (
<div
aria-hidden='true'
className='str-chat__chat-view__selector-button-tooltip str-chat__tooltip'
>
Actions
</div>
)}
</div>
);

export const ActionsMenu = ({ iconOnly = true }: { iconOnly?: boolean }) => (
<DialogManagerProvider id='app-actions-menu-dialog-manager'>
<ActionsMenuInner iconOnly={iconOnly} />
</DialogManagerProvider>
);

const ActionsMenuInner = ({ iconOnly }: { iconOnly: boolean }) => {
const [menuButtonElement, setMenuButtonElement] = useState<HTMLButtonElement | null>(
null,
);
const { dialog: actionsMenuDialog, dialogManager } = useDialogOnNearestManager({
id: actionsMenuDialogId,
});
const { dialog: notificationDialog } = useDialogOnNearestManager({
id: notificationPromptDialogId,
});
const menuIsOpen = useDialogIsOpen(actionsMenuDialogId, dialogManager?.id);

const rootMenuItems = useMemo<ContextMenuItemComponent[]>(
() => [
function TriggerNotification({ closeMenu }) {
return (
<ContextMenuButton
label='Trigger Notification'
onClick={() => {
closeMenu();
notificationDialog.open();
}}
/>
);
},
],
[notificationDialog],
);

return (
<div className='app__actions-menu-anchor'>
<ActionsMenuButton
iconOnly={iconOnly}
isOpen={menuIsOpen}
onClick={() => actionsMenuDialog.toggle()}
refCallback={setMenuButtonElement}
/>
<ContextMenu
backLabel='Back'
className='app__actions-menu'
dialogManagerId={dialogManager?.id}
id={actionsMenuDialogId}
items={rootMenuItems}
onClose={actionsMenuDialog.close}
placement='right-start'
referenceElement={menuButtonElement}
tabIndex={-1}
trapFocus
/>
<NotificationPromptDialog referenceElement={menuButtonElement} />
</div>
);
};
Loading
Loading