Compare commits

..

No commits in common. "cce6889d3538adfe798601d1fa0fc176f0463642" and "63ff45149a60ae3ebcc90dec5f7b41d519d0870f" have entirely different histories.

39 changed files with 237 additions and 713 deletions

View file

@ -1,361 +0,0 @@
name: email
# Phase 0.3 of the nigig-email remediation plan.
#
# Before this file existed, nigig-email had NO CI of any kind. That is
# how a binary with unbalanced braces reached main and stayed there:
# `cargo check -p nigig-email` failed on main while `--lib` passed, so
# the library was fine and the BINARY had never compiled once. Nobody
# had ever run the crate standalone.
#
# It is also how four unused dependencies survived, one of them
# robius-location -- the exact dependency SMS Phase B removed from three
# other crates because it pulls polkit/gio/glib and two RUSTSEC
# advisories in for code that is never called.
#
# Scope note: this crate is a UI shell over nigig-core::email_account,
# email_store and email_worker. The domain logic lives in nigig-core and
# is host-testable; the SMTP transport is not testable here at all
# (no live server in CI), so the tests that matter are the pure ones and
# this workflow gates those plus compilation of both targets.
on:
push:
paths:
- 'crates/apps/nigig-email/**'
- 'crates/nigig-core/src/email_account.rs'
- 'crates/nigig-core/src/email_store.rs'
- 'crates/nigig-core/src/email_worker.rs'
- 'Cargo.lock'
- 'Cargo.toml'
- 'rust-toolchain.toml'
- '.forgejo/workflows/email.yml'
pull_request:
paths:
- 'crates/apps/nigig-email/**'
- 'crates/nigig-core/src/email_account.rs'
- 'crates/nigig-core/src/email_store.rs'
- 'crates/nigig-core/src/email_worker.rs'
- 'Cargo.lock'
- 'Cargo.toml'
- 'rust-toolchain.toml'
- '.forgejo/workflows/email.yml'
jobs:
# ---------------------------------------------------------------------
# Source-scanning gates. No toolchain needed, so they run first and
# fail fast.
# ---------------------------------------------------------------------
gates:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
# S2 of the assessment. SmtpConfig carries a plaintext password and
# derives Serialize, so anything that reuses it for storage leaks
# the credential. EmailAccount is the persistable half and must
# never grow a password field.
#
# There is a unit test asserting the serialised account contains
# neither the secret nor a field named "password"; this is the
# cheaper structural check that catches the field being added even
# if someone deletes the test.
- name: The persistable account type must not carry a password
run: |
set -euo pipefail
# Look only at the EmailAccount struct body.
body=$(sed -n '/^pub struct EmailAccount {/,/^}/p' \
crates/nigig-core/src/email_account.rs)
if echo "$body" | grep -nE '^[[:space:]]*pub[[:space:]]+password'; then
echo
echo "ERROR: EmailAccount declares a password field."
echo "That type is written to disk. The secret is returned"
echo "separately by AccountDraft::validate and held in memory"
echo "for the session only. Persisting it needs the platform"
echo "keystore first (plan item A1 / C1f)."
exit 1
fi
echo "OK"
# B3. `port.parse().unwrap_or(587)` silently rewrote a typo'd port,
# and because the port selects the transport (465 implicit TLS vs
# 587 STARTTLS) that silently changed the security posture too.
# AccountDraft::validate now rejects a malformed port instead.
- name: A malformed port must not silently become a default
run: |
set -euo pipefail
# Exclude comment lines: email_account.rs documents the old
# behaviour by quoting it, and a gate that trips on its own
# rationale is a gate nobody keeps.
if grep -rnE 'parse\(\)[[:space:]]*\.unwrap_or\(587\)' \
crates/apps/nigig-email/src \
crates/nigig-core/src/email_account.rs \
crates/nigig-core/src/email_store.rs \
| grep -vE '^[^:]*:[0-9]*:[[:space:]]*(//|/\*|\*|///)'; then
echo
echo "ERROR: a port is being parsed with unwrap_or(587)."
echo "That turns '465x' into 587 and switches the transport"
echo "from implicit TLS to STARTTLS without telling the user."
echo "Return AccountError::PortInvalid instead."
exit 1
fi
echo "OK"
# A3, the byte-offset slicing class of bug. `&s[..n]` panics when n
# is not a UTF-8 boundary; one inbound message containing emoji or
# non-Latin text took down the whole SMS conversation list on every
# frame until it was deleted. Email bodies are equally untrusted --
# more so, they arrive with arbitrary MIME -- and email_store's
# preview_line runs per visible row inside draw_walk.
#
# The SMS crates carry the same gate. This one is a hard zero
# because the email code has never had such a slice.
- name: No byte-offset string slicing in the email text helpers
run: |
set -euo pipefail
PATTERN='[A-Za-z_][A-Za-z0-9_]*\[(\.\.=?[A-Za-z0-9_]+|[A-Za-z0-9_]+\.\.)[A-Za-z0-9_]*\]'
# flat[..idx] in preview_line is proven safe -- idx comes from
# char_indices() -- and carries a comment saying so. Exclude it
# by line content rather than by muting the whole file.
hits=$(grep -rnE --include='*.rs' "$PATTERN" \
crates/apps/nigig-email/src \
crates/nigig-core/src/email_store.rs \
crates/nigig-core/src/email_account.rs \
| grep -vE '^[^:]*:[0-9]*:[[:space:]]*(//|/\*|\*)' \
| grep -v 'flat\[\.\.idx\]' || true)
if [ -n "$hits" ]; then
echo "$hits"
echo
echo "ERROR: byte-offset slice(s) above. These panic when the"
echo "offset is not a char boundary, which any email body"
echo "containing emoji or non-Latin text will produce. Use"
echo "char_indices() to find a real boundary first."
exit 1
fi
echo "OK"
# The inbox list is populated from email_store::sample_thread()
# because no receive path exists yet (finding A1, plan C1). That is
# acceptable as scaffolding and unacceptable as a shipped state, so
# keep it visible: the name must stay `sample_`-prefixed and must
# not spread beyond the one call site.
- name: Development sample data must stay obvious and contained
run: |
set -euo pipefail
# Count CALL sites, not the import line.
count=$(grep -rn 'sample_thread()' --include='*.rs' \
crates/apps/nigig-email/src | wc -l)
if [ "$count" -gt 1 ]; then
grep -rn 'sample_thread' --include='*.rs' crates/apps/nigig-email/src
echo
echo "ERROR: sample_thread() is referenced $count times in the"
echo "UI. It is placeholder data for one call site until a real"
echo "fetch lands (plan C1/C4b). Spreading it makes the"
echo "placeholder load-bearing."
exit 1
fi
echo "OK"
# ---------------------------------------------------------------------
# The email domain logic. Pure, host-testable, no display and no
# network -- this is the job that can actually prove something.
# ---------------------------------------------------------------------
email-domain:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
# nigig-core pulls sqlite and the matrix client.
- name: Install native dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq \
pkg-config libssl-dev libsqlite3-dev
# NOT actions/setup-rust@v1 -- that action does not exist on this
# instance's registry and fails the job in "Set up job" before any
# step runs. See .forgejo/RUNNER.md.
- name: Install the declared toolchain
run: |
set -e
version="$(sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
rust-toolchain.toml | head -n 1)"
curl --fail --location --proto '=https' --tlsv1.2 https://sh.rustup.rs -o /tmp/rustup-init
chmod 700 /tmp/rustup-init
/tmp/rustup-init -y --profile minimal --default-toolchain "$version" \
--component rustfmt --component clippy --no-modify-path
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Email domain tests
run: cargo test --locked -p nigig-core --lib email_
# A floor, not a ratchet: these tests are cheap, pure, and the
# number should only go up. 38 today.
- name: The email domain test suite must not shrink
run: |
set -euo pipefail
FLOOR=38
out="$(cargo test --locked -p nigig-core --lib email_ 2>&1)"
echo "$out" | grep -E '^test result:' || true
n=$(echo "$out" | grep -E '^test result:' \
| sed -n 's/.* \([0-9]\+\) passed.*/\1/p' \
| awk '{s+=$1} END {print s+0}')
echo "email domain tests: $n (floor $FLOOR)"
if [ "$n" -lt "$FLOOR" ]; then
echo "ERROR: $n < $FLOOR. Tests were deleted or stopped running."
exit 1
fi
echo "OK"
# ---------------------------------------------------------------------
# The UI crate. Pulls the whole Makepad stack, so it is the slow job
# and needs the GUI system libraries.
# ---------------------------------------------------------------------
nigig-email:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Install native dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq \
pkg-config libwayland-dev libxcursor-dev libxrandr-dev \
libxi-dev libx11-dev libgl1-mesa-dev libasound2-dev \
libglib2.0-dev libssl-dev libsqlite3-dev libudev-dev \
libpulse-dev libxkbcommon-dev
- name: Install the declared toolchain
run: |
set -e
version="$(sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
rust-toolchain.toml | head -n 1)"
curl --fail --location --proto '=https' --tlsv1.2 https://sh.rustup.rs -o /tmp/rustup-init
chmod 700 /tmp/rustup-init
/tmp/rustup-init -y --profile minimal --default-toolchain "$version" \
--component rustfmt --component clippy --no-modify-path
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
# --all-targets on purpose. `--lib` alone passed for the entire
# time main.rs was syntactically invalid, which is precisely the
# failure this job exists to prevent.
- name: Check (lib AND bin)
run: cargo check --locked -p nigig-email --all-targets
- name: Test
run: cargo test --locked -p nigig-email
# Phase 0.6. A hard gate, not report-only: this crate is 1,100
# lines and already formats clean, so there is no pre-existing
# drift to grandfather in. Contrast sms.yml and nigig-map.yml,
# which are report-only because they inherited hundreds of diffs.
- name: Formatting
run: |
cargo fmt -p nigig-email -- --check \
|| { echo "run: cargo fmt -p nigig-email"; exit 1; }
# Ratchet at the measured baseline, which is ZERO.
#
# I first set this to 2, having seen two `unexpected_cfgs` warnings
# for `native_activity` emitted by the app_main! macro. Measuring
# properly with the same dedupe this script uses gives 0 -- those
# two are attributed to the bin target and filtered out by the
# package_id check. A baseline above the real count is not a
# harmless margin: this script fails when n < BASELINE precisely so
# that slack cannot hide a regression.
- name: Clippy ratchet (nigig-email-owned diagnostics only)
run: |
set -euo pipefail
BASELINE=0
cargo clippy --locked -p nigig-email --all-targets \
--message-format=json > /tmp/clippy-email.json 2>/tmp/clippy-email.err || true
cat /tmp/clippy-email.err || true
BASELINE="$BASELINE" python3 - <<'PY'
import json, os, sys
baseline = int(os.environ['BASELINE'])
owned, seen = [], set()
with open('/tmp/clippy-email.json') as fh:
for line in fh:
try:
m = json.loads(line)
except ValueError:
continue
if m.get('reason') != 'compiler-message':
continue
if 'nigig-email' not in m.get('package_id', ''):
continue
msg = m['message']
if msg.get('level') not in ('warning', 'error'):
continue
# --all-targets compiles lib and lib-test, duplicating
# every diagnostic; dedupe on rendered text.
key = msg.get('rendered', '')
if key in seen:
continue
seen.add(key)
owned.append(msg)
n = len(owned)
print("found %d nigig-email diagnostics, baseline %d" % (n, baseline))
if n > baseline:
for msg in owned:
sys.stdout.write(msg.get('rendered', ''))
print()
print("ERROR: %d diagnostics, up from %d." % (n, baseline))
sys.exit(1)
if n < baseline:
print()
print("Good: down to %d. Lower BASELINE in this file to %d "
"so the progress cannot be undone." % (n, n))
sys.exit(1)
print("OK")
PY
# ---------------------------------------------------------------------
# Supply chain.
# ---------------------------------------------------------------------
supply-chain:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
# Phase 0.5. Four dependencies were declared and never referenced,
# one of them robius-location, which drags polkit/gio/glib and two
# RUSTSEC advisories in for code that is never called.
- name: nigig-email must not declare dependencies it never uses
run: |
set -euo pipefail
bad=0
# chrono is used by inbox.rs::format_thread_time. Everything
# else in the manifest must appear in src/ under its
# underscored crate name.
for dep in serde serde_json robius-location robius-sms lettre; do
declared=$(grep -cE "^[[:space:]]*${dep}[[:space:]]*=" \
crates/apps/nigig-email/Cargo.toml || true)
[ "$declared" -eq 0 ] && continue
underscored="${dep//-/_}"
used=$(grep -rl "$underscored" crates/apps/nigig-email/src 2>/dev/null | wc -l)
if [ "$used" -eq 0 ]; then
echo "ERROR: nigig-email declares $dep but never uses it."
bad=1
fi
done
if [ "$bad" -ne 0 ]; then
echo
echo "Unused dependencies enlarge the attack surface and the"
echo "licence obligations for no benefit. Remove them."
exit 1
fi
echo "OK"
- name: Lockfile must be committed and current
run: |
set -euo pipefail
test -f Cargo.lock || { echo "ERROR: Cargo.lock is not committed."; exit 1; }
git diff --exit-code -- Cargo.lock
- name: Reject whitespace errors
run: git diff --check "$(git rev-list --max-parents=0 HEAD | tail -1)"..HEAD || git diff --check

View file

@ -101,7 +101,6 @@ jobs:
bad=0
for manifest in \
crates/apps/nigig-build/Cargo.toml \
crates/apps/nigig-email/Cargo.toml \
crates/nigig-core/Cargo.toml \
crates/nigig-uikit/Cargo.toml; do
crate_dir="$(dirname "$manifest")"

315
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -524,14 +524,6 @@ snapshot. Commits are on `main`.
| `5a5b817` | **Phase C2 (early)**`email_account.rs` + `email_store.rs`: session state, account validation, sender-thread grouping, char-safe previews. 38 host tests. |
| `b91f97b` | **Phase 0.1 DONE**`main.rs` braces fixed; the binary compiles for the first time. |
| `18bbb7b` | **Feature: account-gated inbox** — sender list + thread reader reusing `nigig_uikit::shared::conversation`; setup form moved off the Bulk tab; `drafts.rs` deleted (0.4). |
| `cc8a727` | **Phase 0.2 DONE** — 42 abbreviated git revs across 34 crates rewritten to full 40-char SHAs. Label-only change, verified against `Cargo.lock`. |
| `4ae50cb` | **Phase 0.5 DONE**`serde`, `serde_json`, `robius-location` removed; platform-dep gate extended to `nigig-email`. |
| `244c4f2` | **Phase 0.3 + 0.6 DONE**`email.yml` (4 jobs, 11 gates). Writing the gates caught **B2 and B3 still live in `bulk.rs`**; both fixed here. |
**Phase 0 is complete.** All seven items done; 0.7 was fixed upstream.
Every gate in `email.yml` was negative-tested — reverted the fix,
confirmed the gate fails, restored it — rather than merely observed
green.
Verified: `cargo check -p nigig-email --all-targets` → 0 errors;
41 tests pass (38 core + 3 UI helpers); `nigig-email` clippy down to 2
@ -617,12 +609,12 @@ Nothing else can be trusted until the crate builds and something runs it.
| ID | Task |
|---|---|
| ~~0.1~~ | ~~**Fix `main.rs` braces.**~~ **DONE** (`b91f97b`) — was missing the `StandaloneFeatureBody` wrapper; `--lib` had always passed, so only the binary was broken. |
| ~~0.2~~ | ~~**Repair the 40-char rev gate repo-wide.**~~ **DONE** (`f5d003f`) — 42 declarations across 34 crates rewritten to full SHAs (`ecf5a572ab62…`, `5efe6e24c9f7…`). Verified label-only: `Cargo.lock` holds one makepad commit id and zero refs to the old. |
| ~~0.3~~ | ~~**Create `.forgejo/workflows/email.yml`**~~ **DONE** — 4 jobs: `gates` (4 source scans), `email-domain` (38 tests + floor), `nigig-email` (check `--all-targets`, test, fmt, clippy ratchet), `supply-chain` (unused deps, lockfile, whitespace). |
| 0.2 | **Repair the 40-char rev gate repo-wide.** Resolve `5efe6e24c` → full SHA, rewrite all 42 declarations. This is mechanical and must land in its own commit. |
| 0.3 | **Create `.forgejo/workflows/email.yml`**: `gates` (source scans), `nigig-email` (check + clippy + test), `supply-chain` (`cargo deny`, lockfile, unused deps). |
| ~~0.4~~ | ~~**Delete or wire `drafts.rs`.**~~ **DONE** (`18bbb7b`) — deleted. 157 lines never declared in `pages/mod.rs`, so never compiled. |
| ~~0.5~~ | ~~**Drop unused deps.**~~ **DONE** (`e958386`) — removed `serde`, `serde_json`, `robius-location` (all 0 references in `src/`). `chrono` kept: it *is* used by `format_thread_time`, correcting the assessment. Extended the existing platform-dep gate to cover `nigig-email`; negative-tested. |
| ~~0.7~~ | ~~**Unblock `origin/main`.**~~ **DONE upstream** (`005bed1`, not mine) — corrected `i_tree` to `0.19.0`, exactly the fix predicted here. Verified: the workspace resolves and `cargo check -p nigig-email --all-targets` passes on current `main`. |
| ~~0.6~~ | ~~**Add `nigig-email` to a fmt gate.**~~ **DONE** — a HARD gate, not report-only: the crate already formats clean, so there is no pre-existing drift to grandfather in. Contrast `sms.yml`/`nigig-map.yml`, which inherited hundreds of diffs and had to report only. |
| 0.5 | **Drop unused deps**: `serde_json`, `robius-location`. *Revised:* `chrono` is now used (`format_thread_time`) and `serde` is used by `email_account`/`email_store`, so only two remain. Extend the "removed platform deps must not come back" gate to cover `nigig-email`. |
| **0.7** | **NEW, HIGHEST PRIORITY — unblock `origin/main`.** The workspace does not resolve at HEAD, so no crate can be CI-verified. Two causes in sequence: `86c9595` dropped the `maps` feature `pageflipnav` needs; `ce0eaae` fixed that but added `i_tree = "1.0.0"` to `crates/apps/map`, a version crates.io does not have (max published: `0.19.0`). Fix is almost certainly `i_tree = "0.19"`, but that is the map crate's call, not this plan's. Blocks 0.2, 0.3 and every ratchet in Phase E. |
| 0.6 | **Add `nigig-email` to a fmt gate**, or record explicitly why not (SMS chose report-only; email is small enough to just format). |
Exit: `cargo check`/`clippy`/`test -p nigig-email` green on a real runner.
@ -673,10 +665,7 @@ send-only form. **It needs a product decision before any code.**
| C6 | **Bulk pacing.** Email providers rate-limit harder than carriers. Port `SendPacing` from `robius-sms` — already generic arithmetic. |
| C7 | **NEW — pull-to-refresh + background fetch** once C1 lands. The SMS crate's D1/D2 pattern (worker thread → results queue → `SignalToUI` → drained on the UI thread) applies directly; do not fetch from `draw_walk`. |
#### C1 — DECIDED: support both, user-selectable
**Decision (2026-08-16): implement both backends and let the user pick,
with a setup form appropriate to each.**
#### C1 — the decision I need from you
| | IMAP on device | Server-side proxy |
|---|---|---|
@ -686,70 +675,12 @@ with a setup form appropriate to each.**
| wasm | IMAP over raw TCP is impossible in a browser — needs a proxy *anyway* | Same code path on every platform |
| Resolves S2? | No — you still hold the password | **Largely yes** |
This is the right call and it is *cheaper than it sounds*, because wasm
already forces a proxy to exist: supporting only IMAP was never actually
an option for the browser target. So the second backend is not new scope,
it is scope that was already implied.
##### What "both" requires architecturally
The thing that makes this tractable is a **trait boundary**, not two
parallel UIs. One `MailBackend` abstraction with two implementations, and
a `BackendKind` discriminant on the account:
```rust
pub enum BackendKind { ImapSmtp, ProxyApi }
pub trait MailBackend {
async fn verify(&self) -> Result<(), String>;
async fn list_inbox(&self) -> Result<Vec<EmailMessage>, String>;
async fn send(&self, req: &EmailSendRequest) -> Result<(), String>;
}
```
`email_store`'s grouping, `preview_line`, the inbox list, the thread
reader and the unread handling all sit **above** this line and are already
written — they consume `Vec<EmailMessage>` and do not care where it came
from. That was deliberate (see C2) and it is what makes two backends a
contained change rather than a rewrite.
##### Two forms, and the fields genuinely differ
Not one form with a toggle that hides rows — the fields are different
enough that pretending otherwise produces a confusing screen:
| IMAP + SMTP | Proxy API |
|---|---|
| Email address | Email address |
| IMAP server + port | API base URL (**must be HTTPS** — S5) |
| SMTP server + port | API token / credential |
| Username | — |
| Password / app password | — |
| — | Optional account label |
So: a backend chooser first, then the matching form. `AccountDraft` gains
a `BackendKind` and validation branches on it — `validate()` already
returns `Vec<AccountError>` and reports every problem at once, so this
extends cleanly.
##### Sequencing
| ID | Task |
|---|---|
| C1a | `BackendKind` + `MailBackend` trait in `nigig-core`. Host-testable with a fake backend; no network. |
| C1b | Extend `AccountDraft`/`EmailAccount` with `BackendKind`; branch validation. Tests for both shapes, including HTTPS-only enforcement on the proxy URL. |
| C1c | Backend chooser + two forms in `EmailAccountSetup`. |
| C1d | `ProxyApiBackend` — thin HTTP client. Do this **first** of the two: it is smaller, it is the only option on wasm, and it exercises the trait boundary end to end. |
| C1e | `ImapSmtpBackend``async-imap`, native only. Gate behind a feature so wasm builds never pull it in. |
| C1f | Keystore-backed credential storage (A1). Required for IMAP to be usable across restarts; optional for the proxy, which can hold a revocable token instead. |
##### One thing I will not pretend
Offering both **doubles the security surface**, and the IMAP path is the
one that keeps a reusable password on the device. A revocable proxy token
is strictly safer than a password that also unlocks the user's password
resets. So the setup UI should say which is which, plainly, rather than
presenting them as equivalent choices.
My read: **the proxy wins on merit**, mostly because the wasm target
already forces one and because it retires the password-storage problem
rather than mitigating it. But it is infrastructure you may not want to
run, and IMAP-on-device is the only option that works with no backend at
all. This is a product call, not a technical one, which is why I have not
made it.
---

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -5,7 +5,7 @@ edition = "2021"
description = "Map tile renderer with viewport, caching, scheduling, and MVT decoding"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc" }
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572" }
# Polygon operations (used by makepad_map for advanced geometry)
i_overlay = { version = "7.0.3", default-features = false }
@ -14,7 +14,7 @@ i_shape = "1.0.0"
i_tree = "0.19.0"
[dev-dependencies]
makepad-test = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc" }
makepad-test = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572" }
[features]
default = ["map_style"]

View file

@ -6,9 +6,9 @@ description = "Visual regression test application for nigig-map widget"
[dependencies]
<<<<<<< HEAD
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "5efe6e24c9f732e9f11b783757f196f4f1c402b2", features = ["maps"] }
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "5efe6e24c", features = ["maps"] }
=======
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", features = ["maps"] }
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", features = ["maps"] }
>>>>>>> 71b5460 (chore: update makepad fork to latest upstream/dev (abd70f4))
nigig-map = { path = "../.." }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,11 +4,11 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", features = ["test", "csg", "gltf"] }
makepad-code-editor = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-xr = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-ai = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-base64 = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", features = ["test", "csg", "gltf"] }
makepad-code-editor = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
makepad-xr = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
makepad-ai = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
makepad-base64 = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
# makepad-gltf: read-side GLB parser. Used for round-trip validation
# of arch_gltf.rs output (write GLB → load with makepad_gltf → verify).
nigig-core = { path = "../../nigig-core" }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,8 +4,10 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
# Used by inbox.rs::format_thread_time for list-row timestamps.
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = { version = "0.4", features = ["serde"] }
robius-location = { git = "https://github.com/project-robius/robius", rev = "b766e62b0600f5d2ee21cc6995648346fc277bd8" }

View file

@ -1,5 +1,4 @@
use makepad_widgets::*;
use nigig_core::email_account::AccountDraft;
use nigig_core::email_worker::{spawn_send_email, spawn_smtp_test, EmailWorkerAction, SmtpConfig};
script_mod! {
@ -88,45 +87,29 @@ impl Widget for EmailBulkPage {
self.view.handle_event(cx, event, scope);
if let Event::Actions(actions) = event {
// B2: this block used to read five TextInputs and build an
// SmtpConfig on EVERY action event -- ten heap allocations
// per keystroke, per scroll, per timer tick from any widget
// in the app, for a struct only read when a button is
// clicked. Worse, it captured whatever the fields happened
// to hold when an unrelated action fired. Read on click
// instead.
let test_clicked = self.button(cx, ids!(test_btn)).clicked(actions);
let send_clicked = self.button(cx, ids!(send_btn)).clicked(actions);
let server = self.text_input(cx, ids!(server_input)).text();
let port_t = self.text_input(cx, ids!(port_input)).text();
let username = self.text_input(cx, ids!(username_input)).text();
let password = self.text_input(cx, ids!(password_input)).text();
let from = self.text_input(cx, ids!(from_input)).text();
if test_clicked || send_clicked {
match self.read_config(cx) {
Err(msg) => {
// B3: the port used to be parsed with
// `unwrap_or(587)`, so "465x" silently became 587
// -- and because the port selects the transport
// (465 implicit TLS vs 587 STARTTLS) that
// silently changed the security posture too. Say
// so instead.
let id = if test_clicked {
ids!(status_label)
} else {
ids!(send_status)
};
self.label(cx, id).set_text(cx, &msg);
self.view.redraw(cx);
}
Ok(config) => {
self.smtp_config = config.clone();
if test_clicked {
self.label(cx, ids!(status_label))
.set_text(cx, "Testing...");
spawn_smtp_test(config);
}
}
}
let port: u16 = port_t.parse().unwrap_or(587);
self.smtp_config = SmtpConfig {
server: server.clone(),
port,
username: username.clone(),
password: password.clone(),
from: from.clone(),
};
if self.button(cx, ids!(test_btn)).clicked(actions) {
let config = self.smtp_config.clone();
self.label(cx, ids!(status_label))
.set_text(cx, "Testing...");
spawn_smtp_test(config);
}
if send_clicked && !self.smtp_config.server.is_empty() {
if self.button(cx, ids!(send_btn)).clicked(actions) {
let to = self.text_input(cx, ids!(to_input)).text();
let subject = self.text_input(cx, ids!(subject_input)).text();
let message = self.text_input(cx, ids!(message_input)).text();
@ -158,36 +141,3 @@ impl Widget for EmailBulkPage {
self.view.draw_walk(cx, scope, walk)
}
}
impl EmailBulkPage {
/// Read the SMTP form and validate it.
///
/// Routes through `AccountDraft::validate`, which is unit tested in
/// nigig-core, rather than re-implementing parsing here. That is the
/// only reason this page now rejects a malformed port instead of
/// silently rewriting it to 587 (B3).
fn read_config(&mut self, cx: &mut Cx) -> Result<SmtpConfig, String> {
let draft = AccountDraft {
address: self.text_input(cx, ids!(from_input)).text(),
smtp_server: self.text_input(cx, ids!(server_input)).text(),
smtp_port: self.text_input(cx, ids!(port_input)).text(),
username: self.text_input(cx, ids!(username_input)).text(),
password: self.text_input(cx, ids!(password_input)).text(),
display_name: String::new(),
};
match draft.validate() {
Ok((account, password)) => Ok(SmtpConfig {
server: account.smtp_server,
port: account.smtp_port,
username: account.username,
password,
from: account.address,
}),
Err(errors) => Err(errors
.iter()
.map(|e| e.message())
.collect::<Vec<_>>()
.join(" ")),
}
}
}

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -38,7 +38,7 @@ default = ["demo"]
demo = ["nigig-pay-ui/demo"]
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", features = ["test"] }
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", features = ["test"] }
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -34,7 +34,7 @@ default = []
demo = []
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-pay-domain = { path = "../../nigig-pay-domain" }
# Session correlation (review items 2.7 / 5.3). The registry that refuses an

View file

@ -38,7 +38,7 @@ default = ["demo"]
demo = ["nigig-pay-ui/demo"]
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", features = ["test"] }
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", features = ["test"] }
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
robius-ussd = { path = "../../robius-ussd", features = ["serde"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -10,7 +10,7 @@ edition = "2021"
# The example app in makepad-example-map uses the same flag.
# `map_style` belongs to the Nigig map crate; the Robrix Makepad fork exposes
# its built-in map widget through the `maps` feature only.
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", features = ["maps"] }
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", features = ["maps"] }
nigig-map = { path = "../map" }
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
chrono = { version = "0.4", features = ["serde"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -11,14 +11,14 @@ nigig-pdf-document = { path = "../pdf-document" }
nigig-pdf-graphics = { path = "../pdf-graphics" }
# The `test` feature gates makepad-widgets' re-export of makepad-test,
# which tests/ui.rs imports as makepad_widgets::makepad_test.
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", features = ["test"] }
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", features = ["test"] }
[dev-dependencies]
# Both are required, and neither is redundant: `ui.rs` imports the symbols
# through the re-export above, but the `#[makepad_test]` attribute expands
# to an absolute `::makepad_test::` path, which only resolves if the crate
# is also a direct dependency. Dropping either breaks the UI tests.
makepad-test = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", package = "makepad-test" }
makepad-test = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", package = "makepad-test" }
# A binary host so makepad_test can drive the widget through real event
# delivery; the crate itself remains a library.

View file

@ -16,8 +16,8 @@ description = "Makepad widget wrappers for the spreadsheet engine."
#
# For now, using a relative path from this crate's location:
# crates/apps/spreadsheet/spreadsheet-ui/ → ../../../../makepad/widgets
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
spreadsheet-engine = { path = "../spreadsheet-engine" }
[dev-dependencies]
makepad-test = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", package = "makepad-test" }
makepad-test = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", package = "makepad-test" }

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../../nigig-core" }
nigig-uikit = { path = "../../nigig-uikit" }
serde = { version = "1", features = ["derive"] }

View file

@ -21,7 +21,7 @@ nigig-core = { path = "../../nigig-core" }
# matches. If nigig-core uses a path dep, match it here. If it uses git,
# match that.
# makepad-widgets = { git = "https://github.com/makepad/makepad.git", branch = "dev" }
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
# ── Robius integration ───────────────────────────────────────────────────
# robius-use-makepad sets up the right Makepad feature flags for

View file

@ -12,7 +12,7 @@ async-rt = []
[dependencies]
matrix_client = { path = "../matrix_client", default-features = false }
nigig-system-prefs = { path = "../nigig-system-prefs" }
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = { version = "0.4", features = ["serde"] }

View file

@ -5,7 +5,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
nigig-core = { path = "../nigig-core" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

View file

@ -48,7 +48,7 @@ panic = 'abort'
strip = true
[dependencies]
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", default-features = false, features = ["test", "serde", "maps"] }
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", default-features = false, features = ["test", "serde", "maps"] }
robius-use-makepad = "0.1.1"
robius-open = { git = "https://github.com/project-robius/robius", rev = "b766e62b0600f5d2ee21cc6995648346fc277bd8" }
robius-directories = { git = "https://github.com/project-robius/robius", rev = "b766e62b0600f5d2ee21cc6995648346fc277bd8" }