Compare commits
4 commits
63ff45149a
...
cce6889d35
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cce6889d35 | ||
|
|
7751e96c54 | ||
|
|
964fd5d4ef | ||
|
|
34fecf1924 |
39 changed files with 713 additions and 237 deletions
361
.forgejo/workflows/email.yml
Normal file
361
.forgejo/workflows/email.yml
Normal file
|
|
@ -0,0 +1,361 @@
|
||||||
|
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
|
||||||
|
|
@ -101,6 +101,7 @@ jobs:
|
||||||
bad=0
|
bad=0
|
||||||
for manifest in \
|
for manifest in \
|
||||||
crates/apps/nigig-build/Cargo.toml \
|
crates/apps/nigig-build/Cargo.toml \
|
||||||
|
crates/apps/nigig-email/Cargo.toml \
|
||||||
crates/nigig-core/Cargo.toml \
|
crates/nigig-core/Cargo.toml \
|
||||||
crates/nigig-uikit/Cargo.toml; do
|
crates/nigig-uikit/Cargo.toml; do
|
||||||
crate_dir="$(dirname "$manifest")"
|
crate_dir="$(dirname "$manifest")"
|
||||||
|
|
|
||||||
315
Cargo.lock
generated
315
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -524,6 +524,14 @@ 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. |
|
| `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. |
|
| `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). |
|
| `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;
|
Verified: `cargo check -p nigig-email --all-targets` → 0 errors;
|
||||||
41 tests pass (38 core + 3 UI helpers); `nigig-email` clippy down to 2
|
41 tests pass (38 core + 3 UI helpers); `nigig-email` clippy down to 2
|
||||||
|
|
@ -609,12 +617,12 @@ Nothing else can be trusted until the crate builds and something runs it.
|
||||||
| ID | Task |
|
| 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.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.** Resolve `5efe6e24c` → full SHA, rewrite all 42 declarations. This is mechanical and must land in its own commit. |
|
| ~~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`**: `gates` (source scans), `nigig-email` (check + clippy + test), `supply-chain` (`cargo deny`, lockfile, unused deps). |
|
| ~~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.4~~ | ~~**Delete or wire `drafts.rs`.**~~ **DONE** (`18bbb7b`) — deleted. 157 lines never declared in `pages/mod.rs`, so never compiled. |
|
| ~~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**: `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.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** | **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.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**, or record explicitly why not (SMS chose report-only; email is small enough to just format). |
|
| ~~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. |
|
||||||
|
|
||||||
Exit: `cargo check`/`clippy`/`test -p nigig-email` green on a real runner.
|
Exit: `cargo check`/`clippy`/`test -p nigig-email` green on a real runner.
|
||||||
|
|
||||||
|
|
@ -665,7 +673,10 @@ 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. |
|
| 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`. |
|
| 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 — the decision I need from you
|
#### 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.**
|
||||||
|
|
||||||
| | IMAP on device | Server-side proxy |
|
| | IMAP on device | Server-side proxy |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -675,12 +686,70 @@ send-only form. **It needs a product decision before any code.**
|
||||||
| wasm | IMAP over raw TCP is impossible in a browser — needs a proxy *anyway* | Same code path on every platform |
|
| 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** |
|
| Resolves S2? | No — you still hold the password | **Largely yes** |
|
||||||
|
|
||||||
My read: **the proxy wins on merit**, mostly because the wasm target
|
This is the right call and it is *cheaper than it sounds*, because wasm
|
||||||
already forces one and because it retires the password-storage problem
|
already forces a proxy to exist: supporting only IMAP was never actually
|
||||||
rather than mitigating it. But it is infrastructure you may not want to
|
an option for the browser target. So the second backend is not new scope,
|
||||||
run, and IMAP-on-device is the only option that works with no backend at
|
it is scope that was already implied.
|
||||||
all. This is a product call, not a technical one, which is why I have not
|
|
||||||
made it.
|
##### 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||||
description = "Map tile renderer with viewport, caching, scheduling, and MVT decoding"
|
description = "Map tile renderer with viewport, caching, scheduling, and MVT decoding"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572" }
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc" }
|
||||||
|
|
||||||
# Polygon operations (used by makepad_map for advanced geometry)
|
# Polygon operations (used by makepad_map for advanced geometry)
|
||||||
i_overlay = { version = "7.0.3", default-features = false }
|
i_overlay = { version = "7.0.3", default-features = false }
|
||||||
|
|
@ -14,7 +14,7 @@ i_shape = "1.0.0"
|
||||||
i_tree = "0.19.0"
|
i_tree = "0.19.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
makepad-test = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572" }
|
makepad-test = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["map_style"]
|
default = ["map_style"]
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ description = "Visual regression test application for nigig-map widget"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "5efe6e24c", features = ["maps"] }
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "5efe6e24c9f732e9f11b783757f196f4f1c402b2", features = ["maps"] }
|
||||||
=======
|
=======
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", features = ["maps"] }
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", features = ["maps"] }
|
||||||
>>>>>>> 71b5460 (chore: update makepad fork to latest upstream/dev (abd70f4))
|
>>>>>>> 71b5460 (chore: update makepad fork to latest upstream/dev (abd70f4))
|
||||||
nigig-map = { path = "../.." }
|
nigig-map = { path = "../.." }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", features = ["test", "csg", "gltf"] }
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", features = ["test", "csg", "gltf"] }
|
||||||
makepad-code-editor = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-code-editor = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
makepad-xr = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-xr = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
makepad-ai = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-ai = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
makepad-base64 = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-base64 = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
# makepad-gltf: read-side GLB parser. Used for round-trip validation
|
# makepad-gltf: read-side GLB parser. Used for round-trip validation
|
||||||
# of arch_gltf.rs output (write GLB → load with makepad_gltf → verify).
|
# of arch_gltf.rs output (write GLB → load with makepad_gltf → verify).
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,8 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
# Used by inbox.rs::format_thread_time for list-row timestamps.
|
||||||
serde_json = "1"
|
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
robius-location = { git = "https://github.com/project-robius/robius", rev = "b766e62b0600f5d2ee21cc6995648346fc277bd8" }
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use makepad_widgets::*;
|
use makepad_widgets::*;
|
||||||
|
use nigig_core::email_account::AccountDraft;
|
||||||
use nigig_core::email_worker::{spawn_send_email, spawn_smtp_test, EmailWorkerAction, SmtpConfig};
|
use nigig_core::email_worker::{spawn_send_email, spawn_smtp_test, EmailWorkerAction, SmtpConfig};
|
||||||
|
|
||||||
script_mod! {
|
script_mod! {
|
||||||
|
|
@ -87,29 +88,45 @@ impl Widget for EmailBulkPage {
|
||||||
self.view.handle_event(cx, event, scope);
|
self.view.handle_event(cx, event, scope);
|
||||||
|
|
||||||
if let Event::Actions(actions) = event {
|
if let Event::Actions(actions) = event {
|
||||||
let server = self.text_input(cx, ids!(server_input)).text();
|
// B2: this block used to read five TextInputs and build an
|
||||||
let port_t = self.text_input(cx, ids!(port_input)).text();
|
// SmtpConfig on EVERY action event -- ten heap allocations
|
||||||
let username = self.text_input(cx, ids!(username_input)).text();
|
// per keystroke, per scroll, per timer tick from any widget
|
||||||
let password = self.text_input(cx, ids!(password_input)).text();
|
// in the app, for a struct only read when a button is
|
||||||
let from = self.text_input(cx, ids!(from_input)).text();
|
// 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 port: u16 = port_t.parse().unwrap_or(587);
|
if test_clicked || send_clicked {
|
||||||
self.smtp_config = SmtpConfig {
|
match self.read_config(cx) {
|
||||||
server: server.clone(),
|
Err(msg) => {
|
||||||
port,
|
// B3: the port used to be parsed with
|
||||||
username: username.clone(),
|
// `unwrap_or(587)`, so "465x" silently became 587
|
||||||
password: password.clone(),
|
// -- and because the port selects the transport
|
||||||
from: from.clone(),
|
// (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);
|
||||||
if self.button(cx, ids!(test_btn)).clicked(actions) {
|
self.view.redraw(cx);
|
||||||
let config = self.smtp_config.clone();
|
}
|
||||||
|
Ok(config) => {
|
||||||
|
self.smtp_config = config.clone();
|
||||||
|
if test_clicked {
|
||||||
self.label(cx, ids!(status_label))
|
self.label(cx, ids!(status_label))
|
||||||
.set_text(cx, "Testing...");
|
.set_text(cx, "Testing...");
|
||||||
spawn_smtp_test(config);
|
spawn_smtp_test(config);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.button(cx, ids!(send_btn)).clicked(actions) {
|
if send_clicked && !self.smtp_config.server.is_empty() {
|
||||||
let to = self.text_input(cx, ids!(to_input)).text();
|
let to = self.text_input(cx, ids!(to_input)).text();
|
||||||
let subject = self.text_input(cx, ids!(subject_input)).text();
|
let subject = self.text_input(cx, ids!(subject_input)).text();
|
||||||
let message = self.text_input(cx, ids!(message_input)).text();
|
let message = self.text_input(cx, ids!(message_input)).text();
|
||||||
|
|
@ -141,3 +158,36 @@ impl Widget for EmailBulkPage {
|
||||||
self.view.draw_walk(cx, scope, walk)
|
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(" ")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ default = ["demo"]
|
||||||
demo = ["nigig-pay-ui/demo"]
|
demo = ["nigig-pay-ui/demo"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", features = ["test"] }
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", features = ["test"] }
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ default = []
|
||||||
demo = []
|
demo = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-pay-domain = { path = "../../nigig-pay-domain" }
|
nigig-pay-domain = { path = "../../nigig-pay-domain" }
|
||||||
# Session correlation (review items 2.7 / 5.3). The registry that refuses an
|
# Session correlation (review items 2.7 / 5.3). The registry that refuses an
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ default = ["demo"]
|
||||||
demo = ["nigig-pay-ui/demo"]
|
demo = ["nigig-pay-ui/demo"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", features = ["test"] }
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", features = ["test"] }
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
robius-ussd = { path = "../../robius-ussd", features = ["serde"] }
|
robius-ussd = { path = "../../robius-ussd", features = ["serde"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ edition = "2021"
|
||||||
# The example app in makepad-example-map uses the same flag.
|
# The example app in makepad-example-map uses the same flag.
|
||||||
# `map_style` belongs to the Nigig map crate; the Robrix Makepad fork exposes
|
# `map_style` belongs to the Nigig map crate; the Robrix Makepad fork exposes
|
||||||
# its built-in map widget through the `maps` feature only.
|
# its built-in map widget through the `maps` feature only.
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", features = ["maps"] }
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", features = ["maps"] }
|
||||||
nigig-map = { path = "../map" }
|
nigig-map = { path = "../map" }
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,14 @@ nigig-pdf-document = { path = "../pdf-document" }
|
||||||
nigig-pdf-graphics = { path = "../pdf-graphics" }
|
nigig-pdf-graphics = { path = "../pdf-graphics" }
|
||||||
# The `test` feature gates makepad-widgets' re-export of makepad-test,
|
# The `test` feature gates makepad-widgets' re-export of makepad-test,
|
||||||
# which tests/ui.rs imports as makepad_widgets::makepad_test.
|
# which tests/ui.rs imports as makepad_widgets::makepad_test.
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", features = ["test"] }
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", features = ["test"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# Both are required, and neither is redundant: `ui.rs` imports the symbols
|
# Both are required, and neither is redundant: `ui.rs` imports the symbols
|
||||||
# through the re-export above, but the `#[makepad_test]` attribute expands
|
# through the re-export above, but the `#[makepad_test]` attribute expands
|
||||||
# to an absolute `::makepad_test::` path, which only resolves if the crate
|
# to an absolute `::makepad_test::` path, which only resolves if the crate
|
||||||
# is also a direct dependency. Dropping either breaks the UI tests.
|
# is also a direct dependency. Dropping either breaks the UI tests.
|
||||||
makepad-test = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", package = "makepad-test" }
|
makepad-test = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", package = "makepad-test" }
|
||||||
|
|
||||||
# A binary host so makepad_test can drive the widget through real event
|
# A binary host so makepad_test can drive the widget through real event
|
||||||
# delivery; the crate itself remains a library.
|
# delivery; the crate itself remains a library.
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ description = "Makepad widget wrappers for the spreadsheet engine."
|
||||||
#
|
#
|
||||||
# For now, using a relative path from this crate's location:
|
# For now, using a relative path from this crate's location:
|
||||||
# crates/apps/spreadsheet/spreadsheet-ui/ → ../../../../makepad/widgets
|
# crates/apps/spreadsheet/spreadsheet-ui/ → ../../../../makepad/widgets
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
spreadsheet-engine = { path = "../spreadsheet-engine" }
|
spreadsheet-engine = { path = "../spreadsheet-engine" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
makepad-test = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", package = "makepad-test" }
|
makepad-test = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", package = "makepad-test" }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../../nigig-core" }
|
nigig-core = { path = "../../nigig-core" }
|
||||||
nigig-uikit = { path = "../../nigig-uikit" }
|
nigig-uikit = { path = "../../nigig-uikit" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ nigig-core = { path = "../../nigig-core" }
|
||||||
# matches. If nigig-core uses a path dep, match it here. If it uses git,
|
# matches. If nigig-core uses a path dep, match it here. If it uses git,
|
||||||
# match that.
|
# match that.
|
||||||
# makepad-widgets = { git = "https://github.com/makepad/makepad.git", branch = "dev" }
|
# makepad-widgets = { git = "https://github.com/makepad/makepad.git", branch = "dev" }
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
|
|
||||||
# ── Robius integration ───────────────────────────────────────────────────
|
# ── Robius integration ───────────────────────────────────────────────────
|
||||||
# robius-use-makepad sets up the right Makepad feature flags for
|
# robius-use-makepad sets up the right Makepad feature flags for
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ async-rt = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
matrix_client = { path = "../matrix_client", default-features = false }
|
matrix_client = { path = "../matrix_client", default-features = false }
|
||||||
nigig-system-prefs = { path = "../nigig-system-prefs" }
|
nigig-system-prefs = { path = "../nigig-system-prefs" }
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572"}
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc"}
|
||||||
nigig-core = { path = "../nigig-core" }
|
nigig-core = { path = "../nigig-core" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ panic = 'abort'
|
||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572", default-features = false, features = ["test", "serde", "maps"] }
|
makepad-widgets = { git = "https://gitdab.com/andodeki/makepad", rev = "ecf5a572ab62a1c1598909971f602f99083671cc", default-features = false, features = ["test", "serde", "maps"] }
|
||||||
robius-use-makepad = "0.1.1"
|
robius-use-makepad = "0.1.1"
|
||||||
robius-open = { git = "https://github.com/project-robius/robius", rev = "b766e62b0600f5d2ee21cc6995648346fc277bd8" }
|
robius-open = { git = "https://github.com/project-robius/robius", rev = "b766e62b0600f5d2ee21cc6995648346fc277bd8" }
|
||||||
robius-directories = { git = "https://github.com/project-robius/robius", rev = "b766e62b0600f5d2ee21cc6995648346fc277bd8" }
|
robius-directories = { git = "https://github.com/project-robius/robius", rev = "b766e62b0600f5d2ee21cc6995648346fc277bd8" }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue