{"id":36594887,"url":"https://github.com/mhsnook/sunlo","last_synced_at":"2026-02-14T06:03:49.097Z","repository":{"id":252625404,"uuid":"840843388","full_name":"mhsnook/sunlo","owner":"mhsnook","description":"Sunlo, a social language learning app built on tanstack router, react, and supabase, using flash cards and the FSRS spaced-repetition scheduler","archived":false,"fork":false,"pushed_at":"2026-01-12T13:08:05.000Z","size":5276,"stargazers_count":4,"open_issues_count":56,"forks_count":0,"subscribers_count":1,"default_branch":"next","last_synced_at":"2026-01-12T16:33:57.977Z","etag":null,"topics":["flashcards","fsrs","language-learning","react","social","spaced-repetition","supabase","tanstack-query","tanstack-router"],"latest_commit_sha":null,"homepage":"https://sunlo.app","language":"TypeScript","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/mhsnook.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-08-10T21:24:13.000Z","updated_at":"2026-01-11T17:06:26.000Z","dependencies_parsed_at":"2024-08-18T21:30:15.577Z","dependency_job_id":"2b13f39c-e3b3-4cfd-a4d9-e582edc210e7","html_url":"https://github.com/mhsnook/sunlo","commit_stats":null,"previous_names":["michaelsnook/sunlo-tanrouter","michaelsnook/sunlo-tanstack","mhsnook/sunlo-tanstack","mhsnook/sunlo"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/mhsnook/sunlo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhsnook%2Fsunlo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhsnook%2Fsunlo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhsnook%2Fsunlo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhsnook%2Fsunlo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mhsnook","download_url":"https://codeload.github.com/mhsnook/sunlo/tar.gz/refs/heads/next","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhsnook%2Fsunlo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28442248,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-15T00:55:22.719Z","status":"online","status_checked_at":"2026-01-15T02:00:08.019Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["flashcards","fsrs","language-learning","react","social","spaced-repetition","supabase","tanstack-query","tanstack-router"],"created_at":"2026-01-12T08:27:17.928Z","updated_at":"2026-02-14T06:03:49.090Z","avatar_url":"https://github.com/mhsnook.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sunlo.app\n\nA react SPA and a Supabase project.\n\n## Local Setup\n\n**tl;dr:** Install docker desktop, the Supabase CLI, and PNPM packages (`pnpm i`), run the Supabase DB start (or reset) (`supabase start` or `supabase db reset`) and run the vite server (`pnpm dev`).\n\n### Supabase back-end\n\n- Install [Docker Desktop](https://docs.docker.com/desktop/)\n- Install [Supabase cli](https://supabase.com/docs/guides/local-development/cli/getting-started)\n- `supabase start` to set up the Supabase project for the first time\n- `supabase db reset` to run migrations and seeds\n\n### React front-end\n\n- `pnpm install`\n- `cp .env.example .env`\n- populate the environment variables in .env with the outputs from `supabase start`\n- `pnpm dev`\n\nAccess the in the browser at `http://127.0.0.1:5173`.\n\n## Database management\n\n[Full list of Supabase CLI commands is here.](https://supabase.com/docs/reference/cli/supabase-status)\n\n### Migrations\n\n1. Use the local admin at [http://localhost://54323](http://localhost://54323) to make changes to your dev DB. You can point and click to add or modify columns, or use the SQL terminal to create or modify views and functions.\n2. One your feature is working, use `pnpm run migrate` to create migrations based on your local changes.\n3. `pnpm run seeds:schema` to re-create the base.sql schema. Be sure to use a formatter and only commit things you're sure of. (Oftentimes `base.sql` will be created with some key lines removed, like the command that turns on realtime for required tables (because your local doesn't have it turned on), so you have to not commit those deletions.)\n4. `pnpm run types` to regenerate typescript types\n\nThe migrations should run when the main branch deploys. Or you can `supabase db push` to make it so.\n\n[Read more about working with Supabase migrations.](https://supabase.com/docs/guides/local-development/cli/getting-started)\n\n### To Modify the Seeds\n\nWhen adding new features that use new parts of the database, it's a good idea to put in new seeds\nthat mimic both the common uses of the feature and also the edge cases that we want to be sure\nwill still work properly in production. But the seeds file is not a static set of information, so\nsome care must be taken when updating it.\n\nThe easiest way to modify the seeds might be simply by hand editing `seed.sql`. You will notice in\nthe file, most dates are calculated from a formula like this: `now() - interval '20 hours 30 minutes'`.\nThis\nensures that whenever we are working on the app, this review was always created twenty hours and 30 minutes ago.\nSo our features for \"recently added cards\" and \"cards overdue\" and so on will always stay in sync.\nVirtually every record in the seed file uses dates and day_session strings constructed in this way.\n\nBut you can also modify the data in other ways: use the app! use this new feature of yours! Or go\ninto the local admin and modify data there. Then, when you're ready, you can dump the whole seeds\nfile like this: `pnpm run seeds:data`, or modify this command per your needs:\n`supabase db dump --data-only --local \u003e supabase/seed.sql`.\n\nThen, be prepared to heavily curate this file because the seeding process will not have any\nknowledge of our approach to dates, and your PR will be rejected if your seeds do not follow it.\n\n## The React App\n\n- This app is a full SPA as an architectural choice so that we can use Tauri to compile it to\n  native apps.\n-     We use Tanstack's Router in a React app bundled by Vite, as the front end framework.\n- Data is fetched from the Supabase API using Tanstack DB's QueryCollections, loading up\n  whole tables and slices of tables into the local Collections in memory, and then using\n  live queries to combine, select, join, filter, and aggregate the data as the UI requies.\n- Mostly when we mutate data we are using Tanstack Query's useMutation, and in the onSuccess for\n  the mutation we will run an update to the local collection after the response data comes back:\n\n```typescript\nonSuccess: (data) =\u003e {\n\tsomeCollection.utils.writeInsert(SomeSchema.parse(data))\n\ttoast.success('New deck created!')\n}\n```\n\n- Realtime connections are used for friend requests and chat messages. They are quite easy to set\n  up these listeners in a useEffect, to receive updates and then either `utils.writeInsert` or\n  `utils.writeUpdate` the new record. It remains to be seen how they affect performance when the\n  system has more users, so be mindful.\n\n## Deployment\n\nThe app is deployed to **Vercel**.\n\n### Vercel-Specific Features\n\n**Social Previews (Open Graph):** The app uses\n[Vercel Edge Middleware](https://vercel.com/docs/functions/edge-middleware)\n(`middleware.ts`) to serve Open Graph meta tags to social media crawlers. Since the app is\na pure SPA with no SSR, this middleware intercepts crawler requests and returns a minimal\nHTML page with OG tags for link previews on Facebook, Twitter, LinkedIn, WhatsApp, etc.\n\nThis feature **only works on Vercel** - if you deploy elsewhere, social link previews will\nnot work without implementing an equivalent solution for your platform.\n\n## Using Tauri for Native Apps\n\nIt has always been our intention to cross-compile the JS app for use in a Tauri shell to make\nnative versions of the app, but at this time we aren't maintaining or supporting the Tauri build.\n\nThe technology and its industry support are improving rapidly so by the time we are finished with\nsome other core features, it will make sense to come back and take another try at it.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmhsnook%2Fsunlo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmhsnook%2Fsunlo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmhsnook%2Fsunlo/lists"}