This commit is contained in:
randogoth 2026-10-11 08:50:15 +03:00
commit da5992d7ae
7 changed files with 1172 additions and 0 deletions

58
SPEC.md Normal file
View file

@ -0,0 +1,58 @@
# xfay
## Project layout
- `app/` — Flutter application (main codebase)
- `mews-spec/` — Mews Profile definition files
- `nix-android-sdk/` — Nix flake providing the Android SDK for devbox
Flutter version: `3.41.9-sdk-links` (keep in sync with devbox.json).
---
# Product spec
## What it is
A Flutter browser/client for Mews Pages (see mews-spec).
## Approach
Local-first: all data is stored locally in a Drift database. Network operations (Nostr publishing, image upload) are secondary and never block local functionality.
# Architecture directives
Keep files small and focused.
## Layer separation
- `presentation/` — rendering, input, gestures, navigation.
- `application/actors/` — isolate-backed actors for CPU/IO work (image processing, export, file I/O).
- `application/controllers/` — plain coordinators that own signals and handle synchronous UI commands.
- `database/` — Drift tables, DAOs, queries.
- Signals expose UI-consumable state derived from the application layer.
- No business logic in widgets.
## State management — Signals
- Use the `signals` package for reactive UI state.
- Widgets observe signals and dispatch actions to the application layer.
- Derived UI state should be computed, not manually synchronized.
- Do not duplicate state across multiple signals.
## Business logic — Actor Model (`actors` package)
Use the `actors` package only where Dart Isolate isolation is genuinely useful: CPU-heavy work, blocking I/O, or tasks that must not block the UI thread. Do not use it for pure UI state or simple synchronous coordination.
**Rules for all actors and coordinators:**
- Model interactions as explicit sealed command types.
- No business logic in widgets — dispatch commands to the application layer.
- Keep classes small and focused; avoid large catch-all actors.
- Pass references explicitly when communication is needed.
**Isolate constraint:** The `actors` package runs handlers in separate Dart Isolates. Handlers cannot directly update signals on the main isolate. Isolate-backed actors must return results via message passing; the calling code applies results to signals.
## Persistence — Drift
- Use `drift` for all tables, DAOs, and queries.
- Keep database definitions in `database/` separate from UI code.

390
mews-spec/SPEC.md Normal file
View file

@ -0,0 +1,390 @@
# Mews Profile (Draft 0.1)
## 1. Status and introduction
Mews Profile is the format behind the Mews project at mews.page. It is a small-web format built from a strict subset of XHTML Mobile Profile 1.2, a shared default stylesheet, and simple subscription conventions. Mews pages are ordinary web pages: they open in any browser and on WAP 2.0 phones, and dedicated clients can render them with their own presentation.
This is a working draft (0.1). The name keeps the "MP" of XHTML-MP, turning Mobile Profile into Mews Profile. Each version is published at mews.page/spec/\<version> (this one at mews.page/spec/0.1), and mews.page/spec/ always points to the latest. Everything here is open to change.
### Core principle
**Authors provide structure. Readers control presentation.**
Every rule in this spec follows from that principle. Scripts and author styling are excluded because they take control from the reader. The default stylesheet exists so readers without a dedicated client still get good presentation. The conformance marker exists so clients know when they may apply their own presentation.
### Goals
- Pages that are small, fast, readable, and free of tracking.
- No special protocol, server, proxy, or client required to read a page.
- Every conforming page is valid XHTML-MP 1.2 apart from two attributes (section 3), so it works on real WAP 2.0 handsets.
- A format simple enough to write by hand and to implement a client for in a weekend.
## 2. Conventions and terminology
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in RFC 2119.
| Term | Meaning |
| --- | --- |
| Mews page | An XHTML-MP 1.2 document that follows every MUST rule in this spec and carries the conformance marker. |
| Mews site | A set of Mews pages under one registered domain. |
| Author | Whoever writes or publishes Mews pages. |
| Client | Any software that displays Mews pages: a dedicated Mews client, an ordinary browser, or a WAP phone. |
| Mews client | A client that recognizes the conformance marker and applies its own presentation. |
| Default stylesheet | The shared CSS file published with this spec (section 5). |
| Entry | A dated link that a client can treat as a post (section 6). |
## 3. Document format
A Mews page MUST be a well-formed, valid XHTML-MP 1.2 document that also follows the restrictions in section 4, with one exception: it MAY carry the lang and dir attributes, which XHTML-MP lacks. Browsers need lang to know a page's language when it is served as text/html, and dir to show right-to-left text; old WAP browsers ignore both. Apart from these two attributes, the rules only remove or restrict features, so every Mews page is otherwise valid XHTML-MP.
### 3.1 Prologue and doctype
A Mews page MUST begin with an XML declaration specifying UTF-8, followed by the XHTML-MP 1.2 doctype:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN"
"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">
```
The root element MUST be `<html xmlns="http://www.w3.org/1999/xhtml">` and SHOULD carry `xml:lang` and `lang` attributes with the same value, naming the page language, plus `dir="rtl"` for right-to-left languages such as Hebrew and Arabic.
### 3.2 Conformance marker
A Mews page MUST include this element in its `<head>`:
```xml
<meta name="mews-profile" content="0.1" />
```
The `content` value is the spec version the page follows. A Mews client MUST NOT apply Mews presentation to a page without the marker; such a page is ordinary XHTML-MP.
### 3.3 Head
The `<head>` MUST contain a `<title>` and the conformance marker (3.2). It MAY contain `<meta name="description">`, `<meta name="author">`, `<meta name="viewport" content="width=device-width" />`, `<link rel="icon">` pointing to the same site, the stylesheet link (section 5) and feed links (section 6). Nothing else is permitted in the head. Pages SHOULD include the viewport tag, so modern phones show them at readable size.
## 4. Elements and attributes
Mews Profile keeps the structural parts of XHTML-MP 1.2 and removes everything that controls appearance or behavior. Anything not listed in 4.1 is excluded, notably script, event attributes such as onclick, the style element and attribute, object, meta refresh, and big and small.
### 4.1 Permitted elements and attributes
| Group | Elements |
| --- | --- |
| Structure | `html`, `head`, `title`, `body` |
| Headings | `h1`–`h6` |
| Text blocks | `p`, `blockquote`, `pre`, `address`, `hr` |
| Inline text | `em`, `strong`, `b`, `i`, `code`, `kbd`, `samp`, `var`, `q`, `cite`, `abbr`, `dfn`, `br` |
| Links | `a` |
| Lists | `ul`, `ol`, `li`, `dl`, `dt`, `dd` |
| Tables | `table`, `caption`, `tr`, `th`, `td` |
| Images | `img` |
| Forms | `form`, `fieldset`, `label`, `input`, `select`, `option`, `optgroup`, `textarea` |
| Head | `meta`, `link` (as limited in 3.3) |
Only the attributes below are permitted; any other attribute MUST NOT appear. `id`, `title`, `xml:lang`, `lang` and `dir` are permitted on every element in the body; `lang` and `dir` are the Mews extensions described in section 3. `class` is permitted only on `body` (section 5.3).
| Element | Attributes |
| --- | --- |
| `html` | `xmlns`, `xml:lang`, lang, dir |
| `meta` | `name`, `content` |
| `link` | `rel`, `href`, `type`, `title` |
| `a` | `href`, `hreflang`, `rel`, `type`, `accesskey` |
| `img` | `src`, `alt` |
| `blockquote`, `q` | `cite` |
| `ol` | `start` |
| `li` | `value` |
| `table` | `summary` |
| `th`, `td` | `rowspan`, `colspan`, `scope` |
| `form` | `action`, `method`, `enctype` |
| `input` | `type` (`text`, `password`, `checkbox`, `radio`, `submit`, `reset`, `hidden`), `name`, `value`, `checked`, `size`, `maxlength`, `accesskey`, `inputmode` |
| `select` | `name`, `multiple`, `size` |
| `option` | `value`, `selected` |
| `optgroup` | `label` |
| `textarea` | `name`, `rows`, `cols`, `accesskey`, `inputmode` |
| `label` | `for`, `accesskey` |
### 4.2 Rules for kept features
- **Links.** `href` MAY point anywhere. Links to non-Mews pages are allowed; clients decide how to open them.
- **Images.** An `img` MUST have an `alt` attribute: a description, or `alt=""` for decoration. It MUST NOT carry `width`, `height`, `align` or any other attribute that sizes, aligns or styles it. Each image is shown in its own block, at its natural size and never wider than the text column; the default stylesheet does this, and clients SHOULD do the same. Its `src` MUST point to the same site as the page (the same registered domain, so example.com and img.example.com both qualify) and MUST NOT be a `data:` URI. Formats SHOULD be GIF, JPEG or PNG, which every client and old handset can show. Each image SHOULD be under 50 KB. Authors SHOULD strip location and camera metadata. Text SHOULD NOT be presented only as an image; where it is, the alt attribute SHOULD carry that text.
- **Tables.** Tables are for data only. Tables MUST NOT be nested, and SHOULD use `th` for header cells.
- **Forms.** Forms SHOULD use `method="post"` for anything that changes server state.
- **`accesskey`.** Values SHOULD be the digits 0–9, which map to phone keypads.
### 4.3 Size
The page markup SHOULD be under 64 KB and MUST be under 256 KB. The page plus all its images SHOULD be under 320 KB.
## 5. Presentation
Authors MUST NOT style their pages. The only stylesheet a Mews page may reference is the default stylesheet, which gives ordinary browsers and phones good presentation. Mews clients ignore it and use their own.
### 5.1 The default stylesheet
The project publishes a versioned default stylesheet. Below is the complete version 0.1, `mews-0.1.css`; sites copy it unchanged (5.2). Its base rules MUST be valid WAP CSS so handsets can use it; `line-height`, `max-width` and the dark-mode block are enhancements that older parsers skip. Every text and link color meets 7:1 contrast against its background.
```css
/* Mews Profile default stylesheet 0.1 */
/* Base: light theme */
body {
background-color: #faf8f3;
color: #1f1f1f;
font-family: "Atkinson Hyperlegible", Verdana, Tahoma, system-ui, sans-serif;
font-size: 106%;
line-height: 1.5;
margin: 0 auto;
padding: 1em;
max-width: 40em;
}
h1, h2, h3, h4, h5, h6 {
font-weight: bold;
line-height: 1.25;
margin: 1.6em 0 0.5em 0;
}
h1 { font-size: 1.6em; margin-top: 0.5em; }
h2 { font-size: 1.3em; }
h3 { font-size: 1.1em; }
h4, h5, h6 { font-size: 1em; }
p, ul, ol, dl, blockquote, pre, table, address {
margin: 0 0 1em 0;
}
ul, ol { padding-left: 1.5em; }
li { margin-bottom: 0.25em; }
dt { font-weight: bold; }
dd { margin: 0 0 0.5em 1.5em; }
a { color: #1a4f9c; text-decoration: underline; }
a:visited { color: #5b2a86; }
a:focus, a:active { outline: 2px solid #1a4f9c; }
blockquote {
margin-left: 0;
padding-left: 1em;
border-left: 3px solid #c9c4b8;
}
pre, code, kbd, samp {
font-family: "Source Code Pro", Consolas, Menlo, "DejaVu Sans Mono", monospace;
font-size: 0.9em;
}
pre {
background-color: #efece4;
padding: 0.75em;
overflow: auto;
}
hr {
border: 0;
border-top: 1px solid #c9c4b8;
margin: 2em 0;
}
table { border-collapse: collapse; }
th, td {
border: 1px solid #c9c4b8;
padding: 0.3em 0.6em;
text-align: left;
vertical-align: top;
}
caption { font-weight: bold; text-align: left; }
img {
display: block;
max-width: 100%;
height: auto;
border: 0;
}
input, select, textarea { font-size: 1em; font-family: inherit; }
/* Body variants: light */
body.mews-warm { background-color: #fbf4e8; color: #2a2420; }
body.mews-warm a { color: #8a3d14; }
body.mews-cool { background-color: #f3f6fa; color: #1c2430; }
body.mews-cool a { color: #1a4f9c; }
body.mews-green { background-color: #f2f6ef; color: #1f2a1f; }
body.mews-green a { color: #24502a; }
body.mews-mono { background-color: #e8ebe1; color: #1e2219; }
body.mews-mono a { color: #1e2219; }
/* Dark theme: follows the system setting */
@media (prefers-color-scheme: dark) {
body { background-color: #18191b; color: #e3e1dc; }
a { color: #8fb8f5; }
a:visited { color: #c9a3f2; }
a:focus, a:active { outline-color: #8fb8f5; }
blockquote, hr, th, td { border-color: #45464a; }
pre { background-color: #222326; }
body.mews-warm { background-color: #1d1916; color: #e8dfd3; }
body.mews-warm a { color: #f0b48a; }
body.mews-cool { background-color: #161a20; color: #dde4ee; }
body.mews-cool a { color: #8fb8f5; }
body.mews-green { background-color: #161b16; color: #dbe6d8; }
body.mews-green a { color: #9fd39a; }
body.mews-mono { background-color: #1b1d18; color: #c8d0b8; }
body.mews-mono a { color: #c8d0b8; }
}
html { color-scheme: light dark; }
```
### 5.2 Linking it
A Mews page SHOULD link the default stylesheet with exactly one element:
```xml
<link rel="stylesheet" type="text/css" href="mews-0.1.css" />
```
Sites SHOULD host their own copy rather than linking a central URL, so no single server sees traffic across all sites. Authors MUST NOT modify their copy, except to add @font-face rules that load font files from their own site; any other change makes the page non-conforming.
### 5.3 Body variants
To let sites look distinct without author styling, `body` MAY carry one class from this list. The default stylesheet and Mews clients choose how each variant looks.
| Class | Intent |
| --- | --- |
| `mews-warm` | Warm palette |
| `mews-cool` | Cool palette |
| `mews-green` | Green palette |
| `mews-mono` | Monochrome, like an early phone screen |
Mews clients MAY ignore variants and instead derive colors from the hostname.
## 6. Subscriptions
Any Mews page can be subscribed to through dated links. Sites MAY also offer an Atom feed for ordinary feed readers.
### 6.1 Dated links
A link is an entry when it starts a list item (li) and its text begins with a date in `YYYY-MM-DD` format, followed by a space and a title:
```xml
<ul>
<li><a href="2026-10-08-retro-phones.html">2026-10-08 Collecting retro phones</a></li>
<li><a href="2026-09-21-first-post.html">2026-09-21 First post</a></li>
</ul>
```
Rules:
- Entries SHOULD be listed newest first.
- An entry's URL SHOULD NOT change once published; clients use it to tell entries apart.
- A Mews client subscribing to a page MUST treat each entry link as a post and report new ones.
### 6.2 Atom feeds
A site MAY publish an Atom feed and link it from the head:
```xml
<link rel="alternate" type="application/atom+xml" href="feed.xml" title="Posts" />
```
The feed SHOULD contain the same entries as the dated-links page. Entry content in the feed, if any, SHOULD itself follow section 4.
## 7. Transport and serving
Mews pages are served over ordinary HTTP. No special server software is needed.
### 7.1 MIME type
Servers SHOULD send Mews pages as `text/html`, so browsers still display a page that has a small markup error. Servers MAY send `application/xhtml+xml`, or `application/vnd.wap.xhtml+xml`, to clients whose `Accept` header prefers it. Mews clients parse pages as XML whatever the MIME type.
### 7.2 HTTPS and HTTP
Sites SHOULD serve over HTTPS. Because WAP 2.0 handsets cannot negotiate modern TLS, sites MAY also serve the same pages over plain HTTP. A site serving both MUST NOT redirect HTTP requests to HTTPS for clients that cannot follow it; a common approach is to redirect only when the `User-Agent` is a modern browser.
### 7.3 Caching
Servers SHOULD send `Last-Modified` or `ETag` headers so clients and aggregators can check for changes cheaply.
### 7.4 No tracking
Mews pages MUST NOT load resources from other sites. Servers MUST NOT set cookies on requests for Mews pages, except as part of a form the reader submits.
## 8. Clients
Any browser can read a Mews page. This section applies to Mews clients, which present pages their own way.
### 8.1 Requirements
A Mews client:
- MUST detect the conformance marker and apply Mews presentation only to pages that carry it.
- MUST ignore any author CSS, including the default stylesheet, and use its own presentation.
- MUST NOT execute scripts or event attributes, even on a page that wrongly includes them.
- MUST NOT load resources from sites other than the page's own site without the reader's action.
- MUST display a page that is not well-formed as a clear error, or offer to show its text; it MUST NOT silently guess.
### 8.2 Recommendations
A Mews client SHOULD:
- Let the reader choose fonts, sizes, colors and line width.
- Support `accesskey` as keyboard or keypad shortcuts.
- Support subscriptions through dated links (6.1) and Atom (6.2).
- Show a non-Mews page plainly, or hand it to the system browser, rather than refusing it.
- Mark links to non-Mews pages before the reader follows them, where it can tell.
### 8.3 Freedom
Clients MAY present pages in any way that keeps their structure intact, including reflowing tables on narrow screens, or hiding images until requested.
## 9. Validation, directory and versioning
### 9.1 Validation
A page conforms when it passes two checks:
1. **The Mews DTD.** The XHTML-MP 1.2 DTD, restricted as in section 4 and extended with lang and dir, published with each spec version at `mews.page/dtd/mews-<version>.dtd` (this one at `mews.page/dtd/mews-0.1.dtd`). It encodes the permitted elements and attributes (4.1), the head rules (3.3), the body variant values (5.3) and the ban on nested tables (4.2).
2. **Rule checks.** The rules a DTD cannot express: image sources, formats and sizes (4.2), page size (4.3), the default stylesheet link and its unmodified copy (5.2), and no resources from other sites (7.4).
Apart from lang and dir, every page that passes the Mews DTD is also valid XHTML-MP 1.2. Pages keep the XHTML-MP doctype from 3.1; the Mews DTD is used only by validators. The project SHOULD publish a validator that runs both checks and reports each failure with its section number.
The project also hosts a copy of the original XHTML-MP 1.2 DTD at `mews.page/dtd/xhtml-mobile12.dtd`, so validation never depends on the Open Mobile Alliance's server. Browsers and clients MUST NOT fetch either DTD.
### 9.2 Directory
The Mews directory at mews.page lists only sites whose pages pass validation. Listed sites are rechecked periodically, and a site that stops conforming is notified before it is removed.
### 9.3 Versioning
The spec version uses `MAJOR.MINOR`. A minor version may only relax rules or add recommendations, so pages valid under an earlier minor version remain valid. A major version may tighten rules. Clients SHOULD accept any version with a major number they support.
## Appendix A. Example Mews page
A complete conforming page with a posts list and a feed link:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN"
"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Signal Bars</title>
<meta name="mews-profile" content="0.1" />
<meta name="viewport" content="width=device-width" />
<meta name="description" content="Notes on old phones and small screens." />
<link rel="stylesheet" type="text/css" href="mews-0.1.css" />
<link rel="alternate" type="application/atom+xml" href="feed.xml" title="Posts" />
</head>
<body class="mews-green">
<h1>Signal Bars</h1>
<p>Notes on old phones, small screens and <em>slow</em> software.</p>
<h2>Posts</h2>
<ul>
<li><a href="2026-10-08-retro-phones.html" accesskey="1">2026-10-08 Collecting retro phones</a></li>
<li><a href="2026-09-21-first-post.html" accesskey="2">2026-09-21 First post</a></li>
</ul>
<hr />
<p><a href="about.html" accesskey="0">About this site</a></p>
</body>
</html>
```

106
mews-spec/mews-0.1.css Normal file
View file

@ -0,0 +1,106 @@
/* Mews Profile default stylesheet 0.1 */
/* Base: light theme */
body {
background-color: #faf8f3;
color: #1f1f1f;
font-family: "Atkinson Hyperlegible", Verdana, Tahoma, system-ui, sans-serif;
font-size: 106%;
line-height: 1.5;
margin: 0 auto;
padding: 1em;
max-width: 40em;
}
h1, h2, h3, h4, h5, h6 {
font-weight: bold;
line-height: 1.25;
margin: 1.6em 0 0.5em 0;
}
h1 { font-size: 1.6em; margin-top: 0.5em; }
h2 { font-size: 1.3em; }
h3 { font-size: 1.1em; }
h4, h5, h6 { font-size: 1em; }
p, ul, ol, dl, blockquote, pre, table, address {
margin: 0 0 1em 0;
}
ul, ol { padding-left: 1.5em; }
li { margin-bottom: 0.25em; }
dt { font-weight: bold; }
dd { margin: 0 0 0.5em 1.5em; }
a { color: #1a4f9c; text-decoration: underline; }
a:visited { color: #5b2a86; }
a:focus, a:active { outline: 2px solid #1a4f9c; }
blockquote {
margin-left: 0;
padding-left: 1em;
border-left: 3px solid #c9c4b8;
}
pre, code, kbd, samp {
font-family: "Source Code Pro", Consolas, Menlo, "DejaVu Sans Mono", monospace;
font-size: 0.9em;
}
pre {
background-color: #efece4;
padding: 0.75em;
overflow: auto;
}
hr {
border: 0;
border-top: 1px solid #c9c4b8;
margin: 2em 0;
}
table { border-collapse: collapse; }
th, td {
border: 1px solid #c9c4b8;
padding: 0.3em 0.6em;
text-align: left;
vertical-align: top;
}
caption { font-weight: bold; text-align: left; }
img {
display: block;
max-width: 100%;
height: auto;
border: 0;
}
input, select, textarea { font-size: 1em; font-family: inherit; }
/* Body variants: light */
body.mews-warm { background-color: #fbf4e8; color: #2a2420; }
body.mews-warm a { color: #8a3d14; }
body.mews-cool { background-color: #f3f6fa; color: #1c2430; }
body.mews-cool a { color: #1a4f9c; }
body.mews-green { background-color: #f2f6ef; color: #1f2a1f; }
body.mews-green a { color: #24502a; }
body.mews-mono { background-color: #e8ebe1; color: #1e2219; }
body.mews-mono a { color: #1e2219; }
/* Dark theme: follows the system setting */
@media (prefers-color-scheme: dark) {
body { background-color: #18191b; color: #e3e1dc; }
a { color: #8fb8f5; }
a:visited { color: #c9a3f2; }
a:focus, a:active { outline-color: #8fb8f5; }
blockquote, hr, th, td { border-color: #45464a; }
pre { background-color: #222326; }
body.mews-warm { background-color: #1d1916; color: #e8dfd3; }
body.mews-warm a { color: #f0b48a; }
body.mews-cool { background-color: #161a20; color: #dde4ee; }
body.mews-cool a { color: #8fb8f5; }
body.mews-green { background-color: #161b16; color: #dbe6d8; }
body.mews-green a { color: #9fd39a; }
body.mews-mono { background-color: #1b1d18; color: #c8d0b8; }
body.mews-mono a { color: #c8d0b8; }
}
html { color-scheme: light dark; }

396
mews-spec/mews-0.1.dtd Normal file
View file

@ -0,0 +1,396 @@
<!-- Mews Profile 0.1 DTD
Published at mews.page/dtd/mews-0.1.dtd. Browsers and clients must not
fetch this file.
XHTML Mobile Profile 1.2 restricted as in the Mews Profile
Specification 0.1 section 4, extended with lang and dir (section 3).
Maintained by hand and checked against the XHTML Mobile 1.2 driver,
xhtml-mobile12.dtd (OMA-SUP-DTD_xhtml_mobile12-V1_2-20080331-A), by
check_dtd.py. It cannot be generated by stripping the driver: the
driver declares no elements itself, attribute lists only ever add,
and the rules below do not exist in XHTML-MP to be kept.
This file is standalone: it declares every element and attribute
itself, so validation never depends on the Open Mobile Alliance's or
W3C's servers. Pages keep the XHTML-MP doctype (section 3.1); only
validators use this file (section 9.1).
Encoded here: the permitted elements and attributes (4.1), the head
rules (3.3), the body variant classes (5.3) and the ban on nested
tables (4.2). 51 elements are kept from XHTML Mobile 1.2. Dropped:
acronym, base, big, button, div, legend, noscript, object, param,
script, small, span, style, sub, sup, tt. Event attributes and the
style attribute are omitted entirely.
-->
<!-- Datatypes (as in XHTML Modularization) -->
<!ENTITY % Text.datatype "CDATA" >
<!ENTITY % URI.datatype "CDATA" >
<!ENTITY % Number.datatype "CDATA" >
<!ENTITY % LanguageCode.datatype "NMTOKEN" >
<!ENTITY % Character.datatype "CDATA" >
<!-- Common attributes. id, title, xml:lang, lang and dir are permitted
on every element in the body (SPEC.md 4.1); lang and dir are the Mews
extensions over XHTML-MP (SPEC.md 3). -->
<!ENTITY % id.attrib "id ID #IMPLIED" >
<!ENTITY % title.attrib "title CDATA #IMPLIED" >
<!ENTITY % i18n.attrib
"xml:lang %LanguageCode.datatype; #IMPLIED
lang %LanguageCode.datatype; #IMPLIED
dir (ltr | rtl) #IMPLIED" >
<!ENTITY % Common.attrib "%id.attrib; %title.attrib; %i18n.attrib;" >
<!-- Inline classes -->
<!ENTITY % InlStruct.class "br" >
<!ENTITY % InlPhras.class "| em | strong | code | kbd | samp | var | q | cite | abbr | dfn" >
<!ENTITY % InlPres.class "| b | i" >
<!ENTITY % Anchor.class "| a" >
<!ENTITY % InlSpecial.class "| img" >
<!ENTITY % InlFormNoLabel.class "| input | select | textarea" >
<!ENTITY % InlForm.class "%InlFormNoLabel.class; | label" >
<!ENTITY % Inline.extra "" >
<!ENTITY % Inline.class "%InlStruct.class;
%InlPhras.class;
%InlPres.class;
%Anchor.class;
%InlSpecial.class;
%InlForm.class;
%Inline.extra;" >
<!ENTITY % InlNoAnchor.class "%InlStruct.class;
%InlPhras.class;
%InlPres.class;
%InlSpecial.class;
%InlForm.class;
%Inline.extra;" >
<!ENTITY % InlNoLabel.class "%InlStruct.class;
%InlPhras.class;
%InlPres.class;
%Anchor.class;
%InlSpecial.class;
%InlFormNoLabel.class;
%Inline.extra;" >
<!-- Block classes -->
<!ENTITY % Heading.class "h1 | h2 | h3 | h4 | h5 | h6" >
<!ENTITY % List.class "ul | ol | dl" >
<!ENTITY % BlkStruct.class "p" >
<!ENTITY % BlkPhras.class "| pre | blockquote | address" >
<!ENTITY % BlkPres.class "| hr" >
<!ENTITY % Table.class "| table" >
<!ENTITY % Form.class "| form" >
<!ENTITY % Fieldset.class "| fieldset" >
<!ENTITY % Block.extra "" >
<!ENTITY % BlkNoForm.class "%Heading.class;
| %List.class;
| %BlkStruct.class;
%BlkPhras.class;
%BlkPres.class;
%Fieldset.class;
%Block.extra;" >
<!ENTITY % BlkNoTable.class "%BlkNoForm.class;
%Form.class;
%Block.extra;" >
<!ENTITY % Blk.class "%BlkNoTable.class;
%Table.class;
%Block.extra;" >
<!ENTITY % FlowNoTable.class "%Inline.class;
| %BlkNoTable.class;" >
<!-- pre content model, taken verbatim from the driver. It excludes
%InlPres.class; and %InlSpecial.class;, so b, i and img are not
permitted in pre, as in XHTML Mobile 1.2. -->
<!ENTITY % pre.content
"( #PCDATA
| %InlStruct.class;
%InlPhras.class;
%Anchor.class;
%Inline.extra; )*"
>
<!-- Document structure (SPEC.md 4.1) -->
<!--
Only body uses %Blk.class;, so a table can appear directly in the
body and nowhere else, and tables cannot nest (SPEC.md 4.2). body may
carry one class from the variant list (SPEC.md 5.3).
-->
<!ELEMENT html ( head, body ) >
<!ATTLIST html
xmlns CDATA #FIXED "http://www.w3.org/1999/xhtml"
xml:lang %LanguageCode.datatype; #IMPLIED
lang %LanguageCode.datatype; #IMPLIED
dir (ltr | rtl) #IMPLIED
>
<!ELEMENT head ( title, meta, ( meta | link )* ) >
<!ELEMENT title ( #PCDATA )* >
<!ELEMENT body ( %Blk.class; )+ >
<!ATTLIST body
%Common.attrib;
class (mews-warm | mews-cool | mews-green | mews-mono) #IMPLIED
>
<!-- Head elements (SPEC.md 3.3) -->
<!--
The head may contain only title, meta and link. The title comes
first and at least one meta is required; the conformance marker is a
meta (SPEC.md 3.2), but a DTD cannot check its name and content
together, so validators confirm the marker as a rule check (SPEC.md
9.1). link rel is limited to the uses SPEC.md 3.3 names: the
stylesheet, the icon and feed links.
-->
<!ELEMENT meta EMPTY >
<!ATTLIST meta
name NMTOKEN #IMPLIED
content CDATA #REQUIRED
>
<!ELEMENT link EMPTY >
<!ATTLIST link
rel (alternate | icon | stylesheet) #IMPLIED
href %URI.datatype; #IMPLIED
type CDATA #IMPLIED
title CDATA #IMPLIED
>
<!-- Headings (SPEC.md 4.1) -->
<!ELEMENT h1 ( #PCDATA | %Inline.class; )* >
<!ATTLIST h1
%Common.attrib;
>
<!ELEMENT h2 ( #PCDATA | %Inline.class; )* >
<!ATTLIST h2
%Common.attrib;
>
<!ELEMENT h3 ( #PCDATA | %Inline.class; )* >
<!ATTLIST h3
%Common.attrib;
>
<!ELEMENT h4 ( #PCDATA | %Inline.class; )* >
<!ATTLIST h4
%Common.attrib;
>
<!ELEMENT h5 ( #PCDATA | %Inline.class; )* >
<!ATTLIST h5
%Common.attrib;
>
<!ELEMENT h6 ( #PCDATA | %Inline.class; )* >
<!ATTLIST h6
%Common.attrib;
>
<!-- Text blocks (SPEC.md 4.1) -->
<!ELEMENT p ( #PCDATA | %Inline.class; )* >
<!ATTLIST p
%Common.attrib;
>
<!ELEMENT blockquote ( %BlkNoTable.class; )* >
<!ATTLIST blockquote
%Common.attrib;
cite %URI.datatype; #IMPLIED
>
<!ELEMENT pre %pre.content; >
<!ATTLIST pre
%Common.attrib;
>
<!ELEMENT address ( #PCDATA | %Inline.class; )* >
<!ATTLIST address
%Common.attrib;
>
<!ELEMENT hr EMPTY >
<!ATTLIST hr
%Common.attrib;
>
<!-- Inline text (SPEC.md 4.1) -->
<!ELEMENT em ( #PCDATA | %Inline.class; )* >
<!ATTLIST em
%Common.attrib;
>
<!ELEMENT strong ( #PCDATA | %Inline.class; )* >
<!ATTLIST strong
%Common.attrib;
>
<!ELEMENT code ( #PCDATA | %Inline.class; )* >
<!ATTLIST code
%Common.attrib;
>
<!ELEMENT kbd ( #PCDATA | %Inline.class; )* >
<!ATTLIST kbd
%Common.attrib;
>
<!ELEMENT samp ( #PCDATA | %Inline.class; )* >
<!ATTLIST samp
%Common.attrib;
>
<!ELEMENT var ( #PCDATA | %Inline.class; )* >
<!ATTLIST var
%Common.attrib;
>
<!ELEMENT cite ( #PCDATA | %Inline.class; )* >
<!ATTLIST cite
%Common.attrib;
>
<!ELEMENT abbr ( #PCDATA | %Inline.class; )* >
<!ATTLIST abbr
%Common.attrib;
>
<!ELEMENT dfn ( #PCDATA | %Inline.class; )* >
<!ATTLIST dfn
%Common.attrib;
>
<!ELEMENT b ( #PCDATA | %Inline.class; )* >
<!ATTLIST b
%Common.attrib;
>
<!ELEMENT i ( #PCDATA | %Inline.class; )* >
<!ATTLIST i
%Common.attrib;
>
<!ELEMENT q ( #PCDATA | %Inline.class; )* >
<!ATTLIST q
%Common.attrib;
cite %URI.datatype; #IMPLIED
>
<!ELEMENT br EMPTY >
<!ATTLIST br
%Common.attrib;
>
<!ELEMENT a ( #PCDATA | %InlNoAnchor.class; )* >
<!ATTLIST a
%Common.attrib;
href %URI.datatype; #IMPLIED
hreflang %LanguageCode.datatype; #IMPLIED
rel CDATA #IMPLIED
type CDATA #IMPLIED
accesskey %Character.datatype; #IMPLIED
>
<!ELEMENT img EMPTY >
<!ATTLIST img
%Common.attrib;
src %URI.datatype; #REQUIRED
alt %Text.datatype; #REQUIRED
>
<!-- Lists (SPEC.md 4.1) -->
<!ELEMENT ul ( li )+ >
<!ATTLIST ul
%Common.attrib;
>
<!ELEMENT ol ( li )+ >
<!ATTLIST ol
%Common.attrib;
start %Number.datatype; #IMPLIED
>
<!ELEMENT dl ( dt | dd )+ >
<!ATTLIST dl
%Common.attrib;
>
<!ELEMENT li ( #PCDATA | %FlowNoTable.class; )* >
<!ATTLIST li
%Common.attrib;
value %Number.datatype; #IMPLIED
>
<!ELEMENT dt ( #PCDATA | %Inline.class; )* >
<!ATTLIST dt
%Common.attrib;
>
<!ELEMENT dd ( #PCDATA | %FlowNoTable.class; )* >
<!ATTLIST dd
%Common.attrib;
>
<!-- Tables (SPEC.md 4.2) -->
<!--
As in the XHTML Mobile basic tables module, cells take
%FlowNoTable.class; and never a table, so tables cannot nest
(SPEC.md 4.2).
-->
<!ELEMENT table ( caption?, tr+ ) >
<!ATTLIST table
%Common.attrib;
summary %Text.datatype; #IMPLIED
>
<!ELEMENT caption ( #PCDATA | %Inline.class; )* >
<!ATTLIST caption
%Common.attrib;
>
<!ELEMENT tr ( th | td )+ >
<!ATTLIST tr
%Common.attrib;
>
<!ELEMENT th ( #PCDATA | %FlowNoTable.class; )* >
<!ATTLIST th
%Common.attrib;
rowspan %Number.datatype; "1"
colspan %Number.datatype; "1"
scope (row | col) #IMPLIED
>
<!ELEMENT td ( #PCDATA | %FlowNoTable.class; )* >
<!ATTLIST td
%Common.attrib;
rowspan %Number.datatype; "1"
colspan %Number.datatype; "1"
scope (row | col) #IMPLIED
>
<!-- Forms (SPEC.md 4.1, 4.2) -->
<!--
As in the XHTML MP forms module, form takes %BlkNoForm.class;, so
forms cannot nest. fieldset also takes %BlkNoForm.class;, so a
fieldset cannot reintroduce a form.
-->
<!ELEMENT form ( %BlkNoForm.class; )+ >
<!ATTLIST form
%Common.attrib;
action %URI.datatype; #REQUIRED
method (get | post) "get"
enctype CDATA "application/x-www-form-urlencoded"
>
<!ELEMENT fieldset ( #PCDATA | %Inline.class; | %BlkNoForm.class; )* >
<!ATTLIST fieldset
%Common.attrib;
>
<!ELEMENT label ( #PCDATA | %InlNoLabel.class; )* >
<!ATTLIST label
%Common.attrib;
for IDREF #IMPLIED
accesskey %Character.datatype; #IMPLIED
>
<!ELEMENT input EMPTY >
<!ATTLIST input
%Common.attrib;
type (text | password | checkbox | radio | submit | reset | hidden) "text"
name CDATA #IMPLIED
value CDATA #IMPLIED
checked (checked) #IMPLIED
size %Number.datatype; #IMPLIED
maxlength %Number.datatype; #IMPLIED
accesskey %Character.datatype; #IMPLIED
inputmode CDATA #IMPLIED
>
<!ELEMENT select ( optgroup | option )+ >
<!ATTLIST select
%Common.attrib;
name CDATA #IMPLIED
multiple (multiple) #IMPLIED
size %Number.datatype; #IMPLIED
>
<!ELEMENT optgroup ( option )+ >
<!ATTLIST optgroup
%Common.attrib;
label %Text.datatype; #REQUIRED
>
<!ELEMENT option ( #PCDATA )* >
<!ATTLIST option
%Common.attrib;
value CDATA #IMPLIED
selected (selected) #IMPLIED
>
<!ELEMENT textarea ( #PCDATA )* >
<!ATTLIST textarea
%Common.attrib;
name CDATA #IMPLIED
rows %Number.datatype; #REQUIRED
cols %Number.datatype; #REQUIRED
accesskey %Character.datatype; #IMPLIED
inputmode CDATA #IMPLIED
>

View file

@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd -- "$SCRIPT_DIR/../.." && pwd)"
IDEA_DIR="$PROJECT_DIR/.idea"
LIBRARIES_DIR="$IDEA_DIR/libraries"
FLUTTER_SDK="$PROJECT_DIR/.devbox/nix/profile/default"
DART_SDK="$FLUTTER_SDK/bin/cache/dart-sdk"
mkdir -p "$IDEA_DIR"
if [ ! -x "$FLUTTER_SDK/bin/flutter" ]; then
echo "Flutter not found at $FLUTTER_SDK/bin/flutter. Run devbox install to download it." >&2
exit 1
fi
if [ ! -d "$DART_SDK" ]; then
echo "Dart SDK not found at $DART_SDK. Run flutter doctor to populate the cache." >&2
exit 1
fi
mkdir -p "$LIBRARIES_DIR"
cat > "$IDEA_DIR/flutter.xml" <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FlutterSdkSettings">
<option name="FLUTTER_SDK_PATH" value="$PROJECT_DIR$/.devbox/nix/profile/default" />
</component>
</project>
EOF
cat > "$IDEA_DIR/dart.xml" <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DartSdkGlobalLibSettings">
<option name="dartSdkPath" value="$PROJECT_DIR$/.devbox/nix/profile/default/bin/cache/dart-sdk" />
</component>
</project>
EOF
dart_libs=$(find "$DART_SDK/lib" -maxdepth 1 -mindepth 1 -type d -printf '%f\n' | sort)
tmp_file=$(mktemp)
{
echo '<component name="libraryTable">'
echo ' <library name="Dart SDK">'
echo ' <CLASSES>'
for lib in $dart_libs; do
printf ' <root url="file://$PROJECT_DIR$/.devbox/nix/profile/default/bin/cache/dart-sdk/lib/%s" />\n' "$lib"
done
echo ' </CLASSES>'
echo ' <JAVADOC />'
echo ' <SOURCES />'
echo ' </library>'
echo '</component>'
} > "$tmp_file"
mv "$tmp_file" "$LIBRARIES_DIR/Dart_SDK.xml"
echo "Android Studio configured. Flutter SDK: $FLUTTER_SDK"

136
nix-android-sdk/flake.lock generated Normal file
View file

@ -0,0 +1,136 @@
{
"nodes": {
"android-nixpkgs": {
"inputs": {
"devshell": "devshell",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1768249546,
"narHash": "sha256-cpNxcSNveCBSDmocYlPrayaP8CX8ik0SKR4aNbrxOqA=",
"owner": "tadfisher",
"repo": "android-nixpkgs",
"rev": "001ffcb40e86888d38acf7b851f9a1bfac7d3a81",
"type": "github"
},
"original": {
"owner": "tadfisher",
"ref": "stable",
"repo": "android-nixpkgs",
"type": "github"
}
},
"devshell": {
"inputs": {
"nixpkgs": [
"android-nixpkgs",
"nixpkgs"
]
},
"locked": {
"lastModified": 1764011051,
"narHash": "sha256-M7SZyPZiqZUR/EiiBJnmyUbOi5oE/03tCeFrTiUZchI=",
"owner": "numtide",
"repo": "devshell",
"rev": "17ed8d9744ebe70424659b0ef74ad6d41fc87071",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1768127708,
"narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"android-nixpkgs": "android-nixpkgs",
"flake-utils": "flake-utils_2"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

27
nix-android-sdk/flake.nix Normal file
View file

@ -0,0 +1,27 @@
{
description = "A Flake for the Android SDK";
inputs = {
flake-utils.url = "github:numtide/flake-utils";
android-nixpkgs.url = "github:tadfisher/android-nixpkgs/stable";
};
outputs = { self, flake-utils, android-nixpkgs }:
flake-utils.lib.eachSystem ["x86_64-linux" "x86_64-darwin" "aarch64-darwin"] (system: {
packages.android-sdk = android-nixpkgs.sdk."${system}" (sdkPkgs: with sdkPkgs; [
cmdline-tools-latest
build-tools-33-0-0
build-tools-34-0-0
build-tools-35-0-0
build-tools-36-1-0
cmake-3-22-1
platform-tools
platforms-android-33
platforms-android-34
platforms-android-35
platforms-android-36
emulator
ndk-27-0-12077973
]);
});
}