Lattice
연구를 위한 작업공간
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Real-time sprint retrospectives where the action items actually get done. Next.js 16, React 19, TypeScript, Socket.IO, PostgreSQL, Prisma.
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Sprint retrospectives where the action items actually get done.
Live: https://retroloop-one.vercel.app — start a retro and open the invite link in a second browser to see it from two sides. (The API runs on a free instance that sleeps when idle, so the very first request can take up to a minute.)
A real-time retrospective board for teams: everyone writes cards anonymously, the cards are revealed, grouped and voted on, and the retro ends with a short list of action items — each with an owner and a due date, carried into the next retro until it is done. Participants join by link, no sign-up.
Why another retro tool: less than half of retrospective action items get completed in most teams, and existing tools stop at the board. See docs/RESEARCH.md.
| Write — teammates' cards stay hidden until the reveal | Group — drag similar cards together |
| Review — the next retro starts with last time's items | Landing — in English, Ukrainian, Russian and Portuguese |
Phase by phase, one or more commits each:
| Part | Tech |
|---|---|
| Web app | Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS 4 |
| Realtime server | Node.js 24, Fastify 5, Socket.IO 4 |
| Data | PostgreSQL 18 + Prisma 7; Redis 8 to run several server instances (optional with one) |
| Shared contract | @retro/shared — socket events and payload schemas (zod), used by both sides |
| i18n | next-intl: English, Ukrainian, Russian, Portuguese on locale-prefixed routes; message keys type-checked |
| Production | Docker images for both apps, Caddy with automatic HTTPS, see docs/DEPLOY.md |
| Testing | Vitest (unit + integration on real Postgres/Redis), Playwright end-to-end, a load-test script |
| Tooling | npm workspaces, ESLint, tsup, Docker Compose, GitHub Actions |
apps/web Next.js app (UI, i18n messages, operator stats page)
apps/server Fastify + Socket.IO realtime server, Prisma schema and migrations,
load-test and stats scripts
packages/shared Wire protocol and types shared by web and server
e2e/ Playwright end-to-end tests
deploy/ Caddy config for the self-hosted setup
docs/ Research, deployment, performance, screenshots
The web app and the realtime server are separate processes: Next.js serves the UI,
the server keeps the WebSocket connections and owns the board state. Every event
that crosses the wire is declared once in packages/shared, so a change to the
protocol fails type-checking on both sides instead of at runtime.
Requires Node.js 22.12+ (24 recommended) and Docker.
cp .env.example .env
npm install
npm run db:up # PostgreSQL on :5433, Redis on :6380, Mailpit on :8025
npm run db:migrate
npm run dev # web on http://localhost:3001, server on http://localhost:4000
Start a retro on the home page, then open the invite link in another browser (or a private window) to join as a second person.
GET http://localhost:4000/health reports each dependency and answers 503
within a second if one of them is down.
| Stage | What happens | Enforced on the server |
|---|---|---|
| Write | Everyone adds cards | Other people's text is not sent to the browser at all — only a placeholder — so it can't be read in DevTools |
| Group | Cards are revealed; drag a card onto a similar one to group them | Groups are one level deep; deleting a group's top card promotes the next one |
| Vote | Everyone spends a limited number of votes on groups | The limit is checked in a transaction that locks the voter's row: 10 parallel votes with a limit of 3 store exactly 3. Totals stay hidden |
| Discuss | Groups ranked by votes; the facilitator highlights the current topic; the team agrees on action items | Vote totals are revealed only now. Every action item needs an owner and a due date, and there are at most three per retro — checked under a row lock, so parallel adds can't overshoot |
| Wrap up | What was agreed, the team's follow-through score, exports, the link to the next retro |
A team's retros form a series. When the facilitator starts the next retro, it opens with a review of every action item still open from earlier ones: done, not yet (with a new due date), or dropped. Items that are not done keep coming back until they are; the review stage is skipped when nothing is open.
The follow-through score — the share of action items from earlier retros that actually got done — is shown at the review and at the end of every retro. It is also the product's success metric: most teams finish under half of their retro action items (see docs/RESEARCH.md).
Owners can ask for one e-mail reminder the day before an item is due, in their language; the address is deleted as soon as it is sent and never reaches other browsers. Reminders work with any SMTP provider and are simply hidden when none is configured (in development they land in Mailpit at http://localhost:8025).
Agreed items leave the tool the way teams already work: Markdown for a wiki or chat, CSV that Jira's importer maps without setup, a prefilled GitHub issue link, and an .ics calendar event on the due date for the owner.
Whoever creates a retro gets a facilitator key (kept in their browser, stored as a hash); only the facilitator moves between stages, runs the timer and can hand the role over. Card authors are never sent to other participants in any stage — the client only learns "mine" or "not mine". The timer runs on the server clock; each browser corrects for its own clock offset, measured on join.
Every retro ends with a one-question survey — how useful was this retro? — with an optional comment and explicit consent to quote it without a name. Together with anonymous product events (a retro was created, a stage was reached, an export was used — never who, never what was written) this is how the product measures whether it helps.
There are no accounts, no tracking cookies and no ads. A test checks that no team name, participant name, card text or comment ever ends up in an analytics event. Teams with no retro for a year are deleted automatically, and a facilitator can delete a retro at any time. Public endpoints are rate-limited: 10 new teams a minute per address, and each socket gets a token bucket (bursts of 40 events, 10 a second sustained).
With ADMIN_TOKEN set, the operator gets a stats page at /en/admin: how far
teams get (created → discussed → finished → came back for another retro), survey
ratings and the comments people agreed to have quoted, the follow-through rate
on reviewed action items, and new teams per week. npm run stats -w @retro/server
prints the same numbers as a Markdown table for this README.
npm run typecheck
npm run lint
npm test
npm run build
npm run test:e2e # Playwright; reuses `npm run dev` if it is running
End-to-end tests drive several real browsers through whole retros: anonymous writing (the hidden text is checked against the page HTML, not just the screen), drag-and-drop grouping, the vote limit, action items with CSV and calendar downloads, the next retro's review, the Ukrainian locale, the survey and deleting a retro.
Server tests are integration tests: they start the real server and talk to it over
Socket.IO, against a separate retro_test PostgreSQL database (created and migrated
automatically, so npm run db:up must be running). CI runs the same steps on every
push against a fresh PostgreSQL.