{"id":23274598,"url":"https://github.com/ianwold/planningpoker","last_synced_at":"2026-01-05T18:46:35.566Z","repository":{"id":232083444,"uuid":"783427227","full_name":"IanWold/PlanningPoker","owner":"IanWold","description":"A tool for planning poker exercises. Always free, without limits.","archived":false,"fork":false,"pushed_at":"2024-08-01T19:55:03.000Z","size":606,"stargazers_count":1,"open_issues_count":4,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-24T11:47:06.025Z","etag":null,"topics":["blazor","blazor-webassembly","foss","free","planning-poker","railway-app","redis","signalr","unlicense"],"latest_commit_sha":null,"homepage":"https://freeplanningpoker.io","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/IanWold.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-04-07T21:08:46.000Z","updated_at":"2024-08-01T19:55:06.000Z","dependencies_parsed_at":"2024-04-22T14:08:56.543Z","dependency_job_id":null,"html_url":"https://github.com/IanWold/PlanningPoker","commit_stats":null,"previous_names":["ianwold/planningpoker"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IanWold%2FPlanningPoker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IanWold%2FPlanningPoker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IanWold%2FPlanningPoker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IanWold%2FPlanningPoker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/IanWold","download_url":"https://codeload.github.com/IanWold/PlanningPoker/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245733924,"owners_count":20663605,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blazor","blazor-webassembly","foss","free","planning-poker","railway-app","redis","signalr","unlicense"],"created_at":"2024-12-19T20:14:05.041Z","updated_at":"2026-01-05T18:46:35.561Z","avatar_url":"https://github.com/IanWold.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \n\u003cimg src=\"https://raw.githubusercontent.com/IanWold/PlanningPoker/main/logo.png\" height=\"150\"\u003e\n\n# FreePlanningPoker.io\n\n\u003ca href=\"https://freeplanningpoker.io\"\u003e\u003cimg alt=\"Website\" src=\"https://img.shields.io/website?url=https%3A%2F%2Ffreeplanningpoker.io\u0026style=for-the-badge\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/IanWold/PlanningPoker/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22\"\u003e\u003cimg alt=\"GitHub Issues or Pull Requests by label\" src=\"https://img.shields.io/github/issues-search?query=repo%3Aianwold%2Fplanningpoker%20is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22\u0026style=for-the-badge\u0026label=Good%20First%20Issues\u0026color=yellow\"\u003e\u003c/a\u003e\n\nFast and easy planning poker sessions for your whole team: free, secure, and open-source!\n\nAlways Free ♣ Unlimited Sessions ♠ Unlimited Participants\n\n[FreePlanningPoker.io](https://freeplanningpoker.io) •\n[Deploy Yourself](#deploying) •\n[Contribute](#contributing)\n\n\u003c/div\u003e\n\n---\n\nThis is Free Planning Poker, a free tool for software teams to do \"planning poker\" exercises to estimate the difficulty and length of development tasks. You can probably use it for other purposes if you need, too. It's always going to be free, without limits.\n\nNote that I've just started development on this so some documentation and whatnot is a WIP as this gets set up!\n\n# Running Locally\n\nThe ideal scenario is that you can \"clone and go\" without much (if any) work, but there's a couple steps you need right now:\n\n1. [Fork](https://github.com/IanWold/PlanningPoker/fork) and clone this repo\n2. Download and install the [.NET 10 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/10.0)\n3. I recommend using VSCode with the [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit\u0026WT.mc_id=dotnet-35129-website)\n\nYou should be good to go now - hit F5 and watch it run! By default it will use an in-memory store to keep state. This store is _not_ thread safe; in order to get thread safety (and to allow SignalR to use a backplane) you'll need to provide a connection string for Redis. However, the in-memory store is fast and ideal for local debugging scenarios.\n\n## Running with Redis\n\n1. You will need access to _some_ deployment of Redis. [Redis' Quick Start docs](https://redis.io/docs/latest/get-started/) can help you here.\n2. Add your Redis connection string in [appsettings](https://github.com/IanWold/PlanningPoker/blob/main/PlanningPoker.Server/appsettings.Development.json):\n\n```json\n\"ConnectionStrings\": {\n    \"Redis\": \"\u003cyour-connection-string\u003e\"\n}\n```\n\nThe application will see your connection string and use the Redis store instead of the in-memory store, and it will use Redis as a backplane for SignalR.\n\nIn future I want to look into Docker environments to be able to remove standing up your own Redis as being a burden.\n\n## Running with Docker\n\nFreePlanningPoker comes with a standalone [Dockerfile](https://github.com/IanWold/PlanningPoker/blob/main/Dockerfile) that you can run in Docker.\n\n# Deploying\n\nYou can deploy this project yourself without much fuss. I recommend using [Railway](https://railway.app/), my favorite cloud provider for simple apps (heck, even some complicated scenarios are probably fine here).\n\nIn future I want to add some documentation around deploying on Docker, and since this is a .NET app I could include Azure Services documentation easily.\n\n## Via Railway\n\n_(See also my guide on [deploying ASP and Blazor apps on Railway](https://ian.wold.guru/Posts/deploying_aspdotnet_7_projects_with_railway.html))_\n\n1. [Fork](https://github.com/IanWold/PlanningPoker/fork) and clone this repo\n2. Create an account at [Railway](https://railway.app)\n3. Create a [new project](https://docs.railway.app/guides/projects), and [add a Redis instance](https://docs.railway.app/guides/redis) to it\n4. Add a [new service](https://docs.railway.app/guides/services) from your cloned GitHub repo (Railway will handle building and all)\n5. Add your Redis connection string as an environment variable: `ConnectionStrings__Redis` (Use Railway's [reference variables](https://docs.railway.app/guides/variables#reference-variables) to make this easy)\n\nNow you should be good to go! Railway can [provide a domain name](https://docs.railway.app/guides/public-networking#railway-provided-domain) for your instance of FreePlanningPoker so you can use it.\n\nNote that while you technically can deploy this without Redis, I don't recommend it since the in-memory store is not thread safe. If you want to make it thread safe I'd be more than happy to entertain that PR!\n\nIn future I'll be adding some of these settings to a Railway config file in the repo, eliminating the need for a couple of these steps.\n\n## Via Docker\n\nFreePlanningPoker comes with a standalone [Dockerfile](https://github.com/IanWold/PlanningPoker/blob/main/Dockerfile) that you can use to deploy to any containerized environment.\n\n## Via Azure\n\n_This section TBD_.\n\nIf you're hoping to contribute, this would be a good first issue to [add documentation for this](https://github.com/IanWold/PlanningPoker/issues/26)! Realistically, if you have an Azure subscription you should be able to click the Publish button in Visual Studio and send it up in a new App Service.\n\n# Developing\n\nThe client is a Blazor WASM SPA, the server is ASP and they communicate exclusively over SignalR (websockets). The server uses Redis as a backplane for SignalR and to store active sessions - this allows the server to scale horizontally.\n\n\u003ca href=\"https://link.excalidraw.com/readonly/NDvp574BNGntF6oGc3Cg?darkMode=true\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/IanWold/PlanningPoker/main/architecture.png\"\u003e\u003c/a\u003e\n\n## Server\n\nThe SignalR communication is defined by two interfaces in the `PlanningPoker` project: [IServer](https://github.com/IanWold/PlanningPoker/blob/main/PlanningPoker/IServer.cs) defines client-to-server communication (some of which does require a round trip) and [IClient](https://github.com/IanWold/PlanningPoker/blob/main/PlanningPoker/IClient.cs) defines server-to-client communication (none of which requires a round trip; this must be asynchronous communication).\n\nThe logic for the server methods is in [SessionHub](https://github.com/IanWold/PlanningPoker/blob/main/PlanningPoker.Server/SessionHub.cs) in the `Server` project. This class contains the _very minimal_ business rules and the scheme of notifying clients of changes through `IClient`. Clients are grouped by session id, and only clients in a session will receive notifications for it.\n\nState is kept by one of the two classes implementing [IStore](https://github.com/IanWold/PlanningPoker/blob/main/PlanningPoker.Server/IStore.cs): either [InMemoryStore](https://github.com/IanWold/PlanningPoker/blob/main/PlanningPoker.Server/InMemoryStore.cs) or [RedisStore](https://github.com/IanWold/PlanningPoker/blob/main/PlanningPoker.Server/RedisStore.cs). The former is used for local debugging scenarios where Redis isn't strictly needed, while the latter is used for production deployments and any networking-related debugging and testing.\n\nIf you are adding a method on the server for the client to call, you'll update `IServer`, implement the server logic in `SessionHub` and the store classes, then you'll update the client's `SessionState` to call it (see below). If you're adding a method on the client to call, you'll update `IClient`, implement the client logic in `SessionState` (see below), then you'll update the server's `SessionHub` to call down through that method. Everything is strongly-typed by these interfaces on both the client and server, keeping you from needing to using magic strings.\n\nConfiguration and dependency injection are all set up in [Program](https://github.com/IanWold/PlanningPoker/blob/main/PlanningPoker.Server/Program.cs); there's really not a lot there.\n\n## Redis\n\nSession data is stored in Redis across several keys to eliminate or minimize race conditions. The keys and their values are:\n\n* `{sessionId}` (guid): Hash with values \"Title\" and \"State\".\n* `{sessionId}:participants`: List with values being the IDs of the participants in the session.\n* `{sessionId}:participants:{participantId}`: Hash with values \"Name\", \"Points\", and \"Stars\".\n\nAll entries associated with a session are removed from Redis when the last participant leaves the session.\n\n## Client\n\nThere's two main files to care about: [SessionState](https://github.com/IanWold/PlanningPoker/blob/main/PlanningPoker.Client/SessionState.cs) and [SessionPage](https://github.com/IanWold/PlanningPoker/blob/main/PlanningPoker.Client/Pages/SessionPage.razor). The `SessionState` class keeps the state for the user and their session, and handles commands from the UI and notifications from the server which mutate state. As such, it also maintains the SignalR connection and the navigation in the app (this is trivial, that's just moving from the homepage to the session on creation). When the state mutates, the `OnStateChanged` event is raised.\n\n`SessionState` implements `IClient` and keeps an instance of `IServer`, which fulfil the SignalR communication requirements. These are set up in `EnsureInitialized`, and torn down in `LeaveAsync`. Note that `EnsureInitialized` uses some [fancy source generation](https://github.com/IanWold/PlanningPoker/blob/main/PlanningPoker.Client/HubConnectionExtensions.cs) - the [package for this](https://github.com/dotnet/aspnetcore/tree/main/src/SignalR/clients/csharp/Client.SourceGenerator/src) _is_ from Microsoft, though it's undocumented and hasn't been updated in two years. If you dig enough online, you'll find [Kristoffer Strube's post](https://kristoffer-strube.dk/post/typed-signalr-clients-making-type-safe-real-time-communication-in-dotnet/) about them. When adding server functionality, this is the only file you need to change unles the functionality you're adding requires new UI components.\n\n`SessionPage` is the user interface for almost the entire application. The user will first create a session on the homepage (`Index.razor`) but then all the work in the session is done on this page. This page listens to the `OnStateChanged` event from the state and calls `StateHasChanged` on itself when it receives that event. Several components for the UI are broken out into separate Razor components in [the Components directory](https://github.com/IanWold/PlanningPoker/tree/main/PlanningPoker.Client/Components).\n\nDark/light color mode is handled through JS, as is E2E encryption; the code for these functions can be found in [index.html](https://github.com/IanWold/PlanningPoker/blob/main/PlanningPoker.Client/wwwroot/index.html). I wrote a [blog post on E2E encryption](https://ian.wold.guru/Posts/end_to_end_encryption_witn_blazor_wasm.html) which covers the implementation used here.\n\n# Contributing\n\nPlease do! I think the above gives a fair quick overview of the project structure and how to add some features. I've got several [good first issues](https://github.com/IanWold/PlanningPoker/issues) and I'm always happy to discuss suggestions for what to include, modify, etc.\n\nIf you would like to champion an issue, please leave a comment saying you'd like to - I'll assign the issue to you and I'll be happy to clarify any questions.\n\nI don't have formal code standards on this proejct yet; it's quite small and young. I ask that your code be kept minimal, tidy, and in-keeping with the code that's already here. In future as the application solidifies then a more defined coding and architectural standard will probably emerge - I find that a codebase will generally reveal its own standards over time and I prefer allowing that process rather than imposing a (probably wrong) idea on the codebase from the start.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fianwold%2Fplanningpoker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fianwold%2Fplanningpoker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fianwold%2Fplanningpoker/lists"}