# truanayangi keeps every preference in a cookie, and its GitHub Pages site is only a redirect

> A Vietnamese lunch picker that runs on your own machine with no account and no backend, built on Vite and React. The local version is complete; the hosted one lives somewhere else and has to be changed in parallel.

**truanayangi-com/truanayangi** — Trưa Nay Ăn Gì — run locally with pnpm. Automatic cookie storage; no login, backend or cloud services required.

- Repository: https://github.com/truanayangi-com/truanayangi
- Website: https://truanayangi.com
- Stars: 636 · Forks: 138
- Language: TypeScript
- License: not declared
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/truanayangi-com-truanayangi

## start and dev are the same vite command, and build runs the compiler twice over

The script table is short enough to read in full, and two of the entries are identical. `dev` is `vite --host 127.0.0.1`, and `start` is `vite --host 127.0.0.1` as well, so the command the README tells you to run is the development server under a second name. That is why the first-run instructions point at port 5173, which is Vite's default, rather than at anything built.

`build` is `tsc -b && vite build`, so a production build runs the TypeScript project build before Vite bundles anything. `typecheck` is a separate `tsc --noEmit` that skips emit. `preview` is `vite preview --host 127.0.0.1`, and the README puts it at `http://127.0.0.1:4173`.

Running it locally needs Node 22.12 or newer and the pnpm version recorded in the manifest, which is `pnpm@12.3.4`. No `.env` file and no external service configuration is required:

```sh
git clone https://github.com/truanayangi-com/truanayangi.git
cd truanayangi
pnpm install --frozen-lockfile
pnpm start
```

If port 5173 is already taken, the documented alternative is `pnpm start --port 5188`.

## pnpm test runs two files under the test runner and a third outside it

The test script is a single line that chains three things: `node --test tests/cookies.test.mjs tests/case-mechanics.test.mjs && node tests/personal-pool.cjs`.

The first two files go through Node's built-in test runner, and the names tell you what they cover. One is about cookies, which is the storage mechanism the whole application depends on. The other is about the mechanics of the spin, which is the core interaction. The third file is the personal pool, the dish list you build up yourself, and it runs as a plain CommonJS script rather than under the test runner.

Two consequences follow from the shape of that line. The first is that `&&` short-circuits: if either of the two test files fails, `tests/personal-pool.cjs` never runs at all, so a single failure in one suite hides the result of the third. The second is that the personal-pool checks report differently from the other two, whatever their exit conventions are.

A `tests/` directory at the top level and a `typecheck` script that is not wired into `build` are the rest of the quality surface. The contributing guidance says to run the tests and the build when you can and to let the maintainer review before a merge, so the suite is the gate, and how it is invoked is worth knowing before you trust a green run.

## Cookies hold preferences, dish lists and spin history, and there are exactly two failure modes

There is no account, no login and no server-side storage in the local version. Preferences, your own dish lists and the record of spins are all kept in cookies belonging to the browser you are using right now.

The consequences are stated rather than implied. Deleting cookies resets the data. Nothing syncs between devices, so a phone and a laptop hold separate state. The spin history you see is specific to that one browser, which means the count resets with the cookies rather than travelling with you.

Two conditions are called out as failure modes. If cookies are blocked, the application reports that it has not saved. If the dish list gets too large, it reports that it has not saved as well. The same message covers both, and no numeric limit for the cookie payload is given anywhere in the documentation, so there is no way to know where that boundary sits before you hit it.

That single sentence is the whole data model: preferences, dish lists and spin history, all in cookies, all lost together, all local to one browser. For a lunch picker that is a reasonable trade. For anything you expect to come back to after clearing your browser, it is not, and the failure is quiet rather than loud.

## Every script binds loopback, so the app cannot be reached from another device

Loopback is hardcoded rather than defaulted. `dev`, `start` and `preview` all pass `--host 127.0.0.1`, which pins the server to the local interface no matter what you would otherwise want. The documented entry point is `http://127.0.0.1:5173`, and the preview server is `http://127.0.0.1:4173`.

The upside is stated in the README: the server listens only on 127.0.0.1, and once dependencies are installed the application loads its assets from your own machine. External links open only when you click them. There is no third-party fetch on page load.

The downside is that the hardcoded host means you cannot reach the dev server from a phone on the same network, which is the usual way to test a touch interface, without editing the script or overriding the flag on the command line. There is no documented way to do that.

For a single-user local tool this is a reasonable default. It also means that everything you would want to test remotely, including the spin animation on a real screen, has to be done against the hosted site instead.

## gh-pages carries a redirect only, and every UI change has to be made twice

The GitHub Pages site does not host the application. Only the `pages-redirect/` directory is published to the `gh-pages` branch, and that directory redirects to `https://truanayangi.com/`. The application source in this repository is for running locally, and the documentation is explicit that the local build must not be published to `gh-pages`.

The stated reason for the redirect is consistency of function. Visitors always reach the same production frontend, the same API and the same origin for login cookies, instead of a second static application that can drift in features or lose its login on reload.

That reasoning is sound, and it creates a maintenance hazard that the documentation states outright: static UI changes and spin animations have to be updated at the same time in this repository and in the separate private production frontend. There are two copies of the interface, only one of which is public, and nothing in either repository can enforce that they stay in step.

So a fork of this repository gets the local application and nothing of the hosted one. If you wanted to change the interface, you would be editing the copy that nobody else sees.

## Version 1.0.0, no tagged release, no license, and 19 open issues

The manifest declares version `1.0.0` and `"private": false`, so the package is positioned as publishable rather than as an unpublished workspace member. There is no `license` field in it at all, and the top-level tree has no LICENSE file. The repository records an empty license value.

That combination is the awkward one. `"private": false` suggests intent to distribute, an absent license grants nothing, and without a LICENSE file there is no text to point at.

Releases are the other gap. The repository has no GitHub releases, so the `1.0.0` in the manifest has no corresponding tag anywhere and no changelog to go with it. The last push to the default branch is 2026-09-10.

Against that, 636 stars and 136 forks, with 19 open issues. The issue count is high for a repository of this size, and combined with a maintainer-reviewed contribution process that asks people to open pull requests into `main` without needing a prior issue, it suggests a queue that a single maintainer is working through.

For a fork, the practical consequence is that you inherit 19 conversations you cannot read in the issues view of a private mirror, and a licence question you have to answer yourself.

## The project arrived by transfer, keeps an attribution file, and ships a pnpm workspace file with no workspaces

The origin story is documented. The repository was transferred from `nagisanzenin/truanayangi`, keeping the Git history and the community contributions, and an `ATTRIBUTION.md` at the top level records author credits and asset sources. There is a matching `AGENTS.md` beside it.

Documentation is bilingual at the same level. `README.md` is the Vietnamese text and `README.en.md` is the English one, and both point at `https://truanayangi.com` as the official site.

Two small oddities sit in the top-level tree. `pnpm-workspace.yaml` is present, which usually means a monorepo with several packages, yet the manifest declares no `workspaces` field and describes a single package. The lockfile is `pnpm-lock.yaml`, consistent with the `pnpm@12.3.4` pin. Either the workspace file is vestigial from the earlier layout or it is scaffolding for a split that has not happened; nothing in the documentation explains which.

The rest of the tree is what you would expect for a Vite project: `index.html`, `vite.config.ts`, `tsconfig.json`, `public/`, `src/` and `tests/`. The dependency set is React 19.2.8 with `@base-ui/react` at 1.8.0, `lucide-react` at 1.43.0, Tailwind CSS 4.3.3, Vite 8.2.2 and TypeScript 7.0.2.

## Conclusion

Run truanayangi locally if you want the whole thing, since the repository ships the application source and the local build is the complete one. Two things to settle before you rely on it: cookie storage means your dish list and preferences live in one browser and disappear when you clear them, with no size limit stated for the cookie path, and the repository declares no license, so check that before you fork it for anything. If you want the hosted version, remember it is a different codebase maintained by someone else.

## FAQ

### How do I run truanayangi on my own machine?

You need Node 22.12 or newer and the pnpm version in package.json, which is pnpm@12.3.4. Clone, run pnpm install --frozen-lockfile, then pnpm start, and open http://127.0.0.1:5173. If that port is busy, pnpm start --port 5188 uses another one. No .env file is needed.

### Where does truanayangi store my preferences and dish lists?

In cookies in the browser you are using, with no login and no backend. Deleting cookies resets everything, data does not sync between devices, and spin history belongs to that one browser. If cookies are blocked or the dish list is too large, the app reports that it has not saved.

### What does pnpm test run in truanayangi?

The test script runs node --test on tests/cookies.test.mjs and tests/case-mechanics.test.mjs, then runs tests/personal-pool.cjs as a plain script. Because the two are joined with &&, a failure in either test file means the personal-pool script never runs.

### Does the truanayangi GitHub Pages site run the app?

No. Only the pages-redirect/ directory is published to the gh-pages branch, and it redirects to truanayangi.com. The production frontend is a separate private project, which is why static UI and spin animation changes have to be made in both places.

### What license does truanayangi use?

None is declared. The package.json has no license field, there is no LICENSE file in the top-level tree, and the repository records an empty license value even though the manifest sets private to false.

## Sources

- [Issues](https://github.com/truanayangi-com/truanayangi/issues)
- [Project website](https://truanayangi.com)
- [README](https://github.com/truanayangi-com/truanayangi/blob/main/README.md)
- [truanayangi-com/truanayangi on GitHub](https://github.com/truanayangi-com/truanayangi)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/truanayangi-com-truanayangi
