The short version
A genuinely impressive piece of open-source craft -- gorgeous board layouts, a "solvable" generator, per-asset license tagging that puts most jam games to shame -- undone by a core tile-matching click that never once resolved a match across dozens of attempts and four different input methods.
Biggest strengths
- Distinct from Wanderer's existing "Mahjong Solitaire" review (a paid Steam desktop app by a different developer) -- confirmed by reading both entries directly before claiming this slug
- Genuinely excellent presentation: a real onboarding flow, beautiful SVG board-layout thumbnails (Turtle, Fly, Dragon), a "Solvable" board generator option, and a full settings/theme system
- Exemplary asset licensing -- 13 tile-image sets each individually tagged with their real license (CC BY 4.0, GPL-3, public domain, or free) in the source, exactly the kind of per-asset diligence this shelf's licensing warnings ask for
Biggest weaknesses
- Core mechanic did not work in testing: selecting two matching tiles never once removed them from the board, across 30+ candidate pairs tried via real touch dispatch, mouse clicks, the game's own Hint-button-highlighted pairs, and Playwright's element-level click API
- Traced to a likely cause in the shipped code: board-tile.component.ts binds a `data-draw-key` attribute to each tile's host element, and the click handler (board.component.ts) reads that exact attribute to know which tile was hit -- but the attribute is absent from the actual rendered DOM in every build we tested (the live gh-pages site's own assets, fetched and self-hosted both ways)
- We could get as far as selecting a single tile (it visibly highlights) and starting the game timer, so the app is not totally inert -- but a mahjong solitaire game whose matches don't resolve has no completable loop
Who it's for: Nobody, until the underlying issue we found is fixed -- then this could be an easy recommend given how well everything else is built.
Our recommendation: not recommended
4.2 / 10
Mah is an open-source mahjong solitaire by ffalt (146 GitHub stars, actively maintained, built with Angular) that does almost everything right on paper: a proper onboarding flow with an optional interactive tutorial, gorgeous SVG board-layout previews, a "Solvable" board generator so you're never handed an unsolvable deal, and -- unusually for a hobby project -- a fully documented, per-image license breakdown for every one of its thirteen tile-art sets. It's also worth noting up front that this is a completely different game from Wanderer's existing "Mahjong Solitaire" review (a paid Steam release by Albireo Games); we checked both before claiming this slug.
The problem. We could not get a single tile match to actually resolve. We tried: real touch dispatch on matching tile pairs found by reading each tile's tooltip text directly from the DOM (30-plus candidate pairs, zero successes); the same pairs via mouse clicks on a desktop viewport; the exact pair highlighted by the game's own Hint button; and finally Playwright's element-level click API targeting the tile elements directly. In every case, the board's tile count never dropped, and no match ever registered -- despite the game visibly acknowledging a first tap (a tile highlights on selection, and a run timer starts ticking).
Digging into why: the click handler in board.component.ts identifies which tile was hit by reading a data-draw-key attribute that board-tile.component.ts is supposed to bind onto each tile's host element ('[attr.data-draw-key]': 'draw().key'). In every build we tested -- including a fresh clone of the project's own published gh-pages branch, self-hosted with a corrected relative <base href> exactly as recommended -- that attribute is simply absent from the rendered DOM. Without it, the click handler has no way to know which stone you tapped, and the match can never be found.
Everything around the bug is well made. Visuals, board variety, licensing hygiene, and the absence of any tracking or ads are all genuinely commendable, and this project deserves real credit for how carefully it's put together. But a mahjong solitaire game where matches don't resolve isn't one we can send anyone to, and we'd rather flag the specific, reproducible cause clearly than guess at a workaround. This is worth a second look if the underlying build is fixed.
Pros and cons
Pros
- Distinct from Wanderer's existing "Mahjong Solitaire" review (a paid Steam desktop app by a different developer) -- confirmed by reading both entries directly before claiming this slug
- Genuinely excellent presentation: a real onboarding flow, beautiful SVG board-layout thumbnails (Turtle, Fly, Dragon), a "Solvable" board generator option, and a full settings/theme system
- Exemplary asset licensing -- 13 tile-image sets each individually tagged with their real license (CC BY 4.0, GPL-3, public domain, or free) in the source, exactly the kind of per-asset diligence this shelf's licensing warnings ask for
- Zero console/page errors and a correctly responsive layout on both phone and desktop viewports
Cons
- Core mechanic did not work in testing: selecting two matching tiles never once removed them from the board, across 30+ candidate pairs tried via real touch dispatch, mouse clicks, the game's own Hint-button-highlighted pairs, and Playwright's element-level click API
- Traced to a likely cause in the shipped code: board-tile.component.ts binds a `data-draw-key` attribute to each tile's host element, and the click handler (board.component.ts) reads that exact attribute to know which tile was hit -- but the attribute is absent from the actual rendered DOM in every build we tested (the live gh-pages site's own assets, fetched and self-hosted both ways)
- We could get as far as selecting a single tile (it visibly highlights) and starting the game timer, so the app is not totally inert -- but a mahjong solitaire game whose matches don't resolve has no completable loop