Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Dinder — real-time group restaurant decisions. Swipe, match, eat: Socket.IO rooms over Redis with 30-minute TTL sessions and SINTER-based consensus.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
What are we doing tonight? YupCrew helps friends, couples and housemates choose somewhere to eat, takeaway, a recipe to cook, or a movie or series to watch. Invite your people with a link, choose together and swipe to find a shared pick. No download or account needed.
Formerly Dinder. Public address: yupcrew.com. Existing Dinder addresses remain available for current Sessions and Shopping Lists; internal service and storage identifiers are unchanged.README 전문 보기 ↗What are we doing tonight? YupCrew helps friends, couples and housemates choose somewhere to eat, takeaway, a recipe to cook, or a movie or series to watch. Invite your people with a link, choose together and swipe to find a shared pick. No download or account needed.
Formerly Dinder. Public address: yupcrew.com. Existing Dinder addresses remain available for current Sessions and Shopping Lists; internal service and storage identifiers are unchanged.
SINTER when the last person submits.session:expired so clients aren't left polling a dead session.sequenceDiagram
participant H as Host
participant P as Participant
participant S as Backend
participant R as Redis
H->>S: POST /api/sessions
S->>R: create session keys (30-min TTL)
S-->>H: session code
P->>S: session:join
S->>R: add participant, refresh TTL (Lua)
S-->>H: participant:joined
H->>S: selection:submit (liked place IDs)
S->>R: SADD likes, refresh TTL (Lua)
S-->>P: participant:submitted
P->>S: selection:submit
S->>R: SINTER across participant sets
S-->>H: session:results (matches)
S-->>P: session:results (matches)
Note over S,R: if 30 min pass, keys expire — keyspace notification → session:expired
The full client/server event contract — session:join, selection:submit, session:restart, session:leave inbound; participant:joined/submitted/left/disconnected, session:results, session:restarted, session:expired outbound — is typed once in shared/types/websocket-events.ts and imported by both sides, so the frontend and backend cannot drift apart silently.
npm workspaces monorepo, three packages:
| Package | What it is |
|---|---|
backend/ | Node 22 + TypeScript, Express 4, Socket.IO 4.7, ioredis, Zod validation. One handler file per socket event, services for sessions, friends, and restaurant search. |
frontend/ | React 18 + Vite, Tailwind, Zustand for state, socket.io-client. Mobile-first. |
shared/ | @dinder/shared — the typed WebSocket event contract and Zod schemas both sides import. |
The Redis data model is the app. A session is a handful of keys (session:<code>, session:<code>:participants, session:<code>:<participantId>:selections, …), all carrying the same 30-minute TTL. Consensus is SINTER over the per-participant selection sets, and TTL refresh happens in one atomic inline Lua script so a session's keys can never expire out of sync — all in sessionStore.ts. sessionExpiryNotifier.ts subscribes to keyspace expired events to tell connected clients the moment a session dies.
REST is deliberately thin — POST /api/sessions, GET /api/sessions/:code — everything live goes over the socket.
Prerequisites: Node 22, Docker (for Redis), and a Google Places API key.
git clone https://github.com/Zacplischka/dinner_app.git
cd dinner_app
npm install
# Redis
docker run -d -p 6379:6379 redis:7-alpine
# the backend reads backend/.env
echo "GOOGLE_PLACES_API_KEY=your-key-here" > backend/.env
# backend on http://localhost:3001, frontend on http://localhost:3000
npm run dev
| Variable | Needed for |
|---|---|
GOOGLE_PLACES_API_KEY | Restaurant search for Eat Out and Takeaway Sessions |
SPOONACULAR_API_KEY | Sourced Recipes for the Cook Branch |
PORT, REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, FRONTEND_URL | All have local defaults |
SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY | Optional Google sign-in / friends feature only |
VITE_BACKEND_URL | Frontend → backend (REST under /api, and the socket); defaults to the local backend on port 3001 |
VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY | Frontend sign-in; without them auth is disabled with a console warning |
Everything runs from the repo root. shared/ builds first — backend and frontend import @dinder/shared as a file dependency — and build and typecheck take care of that.
npm run dev # backend + frontend together
npm run build # shared → backend → frontend, in that order
npm test # backend + frontend vitest suites (the backend's include contract + integration, which need Redis)
npm run typecheck # builds shared, then tsc --noEmit on both
npm run lint # eslint both workspaces
npm run format # prettier both workspaces
npm run gen:types # regenerate supabase/database.types.ts
Narrower runs go through the workspace: npm run test:unit --workspace=backend, npm run test:contract --workspace=backend (Redis required), npm run test:e2e --workspace=frontend (Playwright; mobile-chrome is the primary project). The WebSocket contract is typed once in shared/types/websocket-events.ts and the contract tests assert the backend against it — they are the source of truth for the realtime protocol.
CI (ci-cd.yml) runs typecheck, lint and both unit suites in one job and the contract suite against a Redis service in another; a green main auto-deploys.
Both services deploy to Railway: the backend via Railpack (npm ci && npm run build && npm run start from the repo root), the frontend as a static SPA.
CONTEXT.md defines domain language; ADRs record architecture decisions.
MIT © Zac Plischka