Files
runway/docs/legacy-audit.md
connor bf63024711 Set up workspace skeleton
Five crates: runway-core (pure domain), runway-caldav (protocol),
runway-server (axum), runway-web (leptos), runway-cli (smoke tool).

runway-core is feature-gated into model/ical/recurrence so the frontend
can depend on the shared types without pulling icalendar and rrule into
the WASM bundle. The previous iteration shipped reqwest, ical and regex
to the browser for a dead module; the feature split makes that mistake
structurally hard to repeat.

Guardrails are compiler- and CI-enforced rather than aspirational:
workspace lints deny unwrap_used/expect_used, dead_code and unsafe_code,
clippy.toml caps function length and arity, deny.toml pins licences.

Toolchain is pinned per-project so the machine-wide default is untouched.
Cargo.lock is committed this time.

docs/legacy-audit.md carries the marked-up feature decisions and is the
spec for the rewrite.
2026-08-26 12:07:44 -04:00

29 KiB
Raw Permalink Blame History

Runway v1 audit — what to carry forward, what to leave behind

Source: /home/connor/docs/projects/calendar (Rust/WASM CalDAV web client, "Runway"). 221 commits · 62 Rust files · ~22,056 lines of Rust · ~8,300 lines of hand-written CSS · 2.5 MB WASM bundle.

Purpose of this doc: an inventory you can mark up. Tick what matters, strike what doesn't, and we'll turn the survivors into a build plan for the rewrite.

How to mark up. Nothing is being ported: every feature you pick gets written fresh, so this list is about what the app should do, not which code survives.

In Part 1 the first three columns are:

  • B — this feature matters; build it. A vote for the capability, not an endorsement of how v1 did it. Assume I'll design the execution fresh, and that anything marked "Works, fragile" gets a better approach by default rather than a faithful reproduction.
  • S — skip it, don't implement it at all.
  • D — it matters and you already have a specific change in mind. Append it to the Notes cell; that's the instruction I'll design against. As loose as "simpler" or as specific as you like.

So B vs D is not "as-is" vs "changed" — both get written from scratch. The difference is whether you're handing me a constraint or leaving the execution to me. The v1 status column is there as history and as a warning about where the hard parts were; it is never a spec.

Part 2 uses Y/N/D — make the same architectural call again, make a different one, or keep the intent but change the execution (again, say how in Notes).

Each cell is a single space at a fixed character position — columns 2, 4 and 6 of every row — so you can move down a column in vim and hit rx. Leave all three blank if you're undecided.


Part 1 — Feature inventory

Notes flag what each feature actually cost in v1.

1.1 Authentication & session

B S D # Feature v1 status Notes
x F1 Log in with CalDAV server URL + username + password Works Auth = "can we PROPFIND your calendars?" Simple and correct. -- I'd like to add OpenID as well, to connect behind my Authelia instance
x F2 JWT token issued by backend, stored in localStorage Works
x F3 Second, parallel session_token (SQLite-backed) used only by the preferences API Works Two auth schemes for one app. Pick one.
x F4 CalDAV password re-sent on every request via X-CalDAV-Password header Works Password is kept in localStorage in cleartext and read from 8+ call sites. See §3.4. -- Keep, but if there's a better way I'm open to it
x F5 "Remember me" — persists server URL + username (not password) Works Cheap to build.
x F6 Token verification on app startup, auto-logout on invalid Works
x F7 Session expiry (24h) + expired-session cleanup Works
x F8 Multi-user support (users table keyed on username+server_url) Works Was this ever used, or is it a single-user app? Big simplification if single-user. -- Currently not used, but I want to keep

1.2 Calendar & event viewing

B S D # Feature v1 status Notes
x F9 Month view Works 326 lines. The cheap one.
x F10 Week view with hour grid Works 1,431 lines — the single hardest file in the repo.
x F11 Day view Absent Never built.
x F12 Agenda / list view Absent Never built.
x F13 Year view Absent Never built.
x F14 Current-time indicator line in week view Works Re-renders every 5s.
x F15 Configurable time increment (grid granularity) Works Persisted per-user.
x F16 Overlapping-event column layout in week view Works, fragile Hand-rolled interval-packing inline in the view component.
x F17 All-day event lane at top of week view Works
x F18 Per-calendar colors Works
x F19 Calendar show/hide toggles in sidebar Works Filtering happens client-side after fetching everything.
x F20 "More events" overflow indicator in month cells Works
x F21 Reminder-bell icon on events that have alarms Works
x F22 Navigate prev/next period, jump to today Works
x F23 Selected-date persistence across reloads Works Stored in localStorage and the DB.

1.3 Event editing

B S D # Feature v1 status Notes
x F24 Create event (tabbed modal: Basic / Advanced / People / Categories / Location / Reminders) Works 6 tabs, ~1,400 lines across event_form/.
x F25 Edit existing event Works
x F26 Delete event Works
x F27 View event detail modal Works
x F28 Drag to move an event (week view) Works, fragile Hand-rolled mouse-event math with pixel offsets and snapping.
x F29 Drag top/bottom edge to resize (week view) Works, fragile Same.
x F30 Drag on empty grid to create an event Works
x F31 Right-click context menu on an event Works
x F32 Right-click context menu on a day cell Works
x F33 Full RFC 5545 property support in the model (status, class, priority, organizer, attendees, categories, geo, url, resources, contact, related-to, sequence, transp, attachments) Model complete, UI partial The VEvent struct covers all of it; the form exposes maybe half; the wire format flattens most of it to comma-separated strings.
x F34 Attendees Half-built Backend has // TODO: Parse attendees properly. Round-trip is lossy.
x F35 Attachments Model only No UI, no CalDAV round-trip.

1.4 Recurring events

This is where most of the complexity — and most of the bugs — lived.

B S D # Feature v1 status Notes
x F36 Create recurring event (daily / weekly / monthly / yearly + interval) Works
x F37 Weekly BYDAY (pick days of week) Works Sent as Vec<bool> of length 7, not as an RRULE. -- If RRULE is better, we should do that
x F38 COUNT limit / UNTIL date Works
x F39 Monthly BYMONTHDAY and BYDAY ("first Monday") Dead-ended Fields exist in the form model (monthly_by_day, monthly_by_monthday, yearly_by_month) but are dropped on the way to the API.
x F40 Client-side expansion of RRULE into visible occurrences Works, ~650 lines hand-rolled See §3.1 — the single biggest reinvention in the repo. -- To be clear, I want to see re-occurences visually, but we don't need to re-invent anything
x F41 Edit scope: "this event only" (EXDATE + RECURRENCE-ID exception) Works Correct RFC approach. Documented well in-code.
x F42 Edit scope: "this and future" (UNTIL split + new series) Works Correct RFC approach.
x F43 Edit scope: "entire series" Works
x F44 Delete scope: this / following / series Works
x F45 EXDATE handling Works
x F46 RECURRENCE-ID exception events Works
F47 Dedicated /api/calendar/events/series/* endpoints Works 1,165 lines of handler. Mostly duplicating the non-series handlers. -- I'm ambivalent on this unless it serves the UI in some useful way

1.5 Calendar management

B S D # Feature v1 status Notes
x F48 Discover calendars on the CalDAV server (PROPFIND) Works
x F49 Create a new calendar collection (MKCALENDAR) Works
x F50 Delete a calendar collection Works
x F51 Calendar management modal (rename/recolor/reorder) Works 448 lines.
x F52 Auto-assign a color per calendar from a hash of its path Works Nice touch.
x F53 Editable 16-color event palette + custom palette editor Works Stored as a JSON blob inside the calendar_colors preferences column.

1.6 External calendar subscriptions

B S D # Feature v1 status Notes
x F54 Subscribe to read-only ICS feed by URL Works
x F55 Server-side caching of fetched ICS with staleness check Works Good idea, worth building.
x F56 Per-feed name, color, visibility Works
x F57 Heuristic de-duplication / consolidation of feed events Actively harmful ~500 lines that fuzzy-match events by normalized title and merge or discard them. See §3.2.
x F58 ETag support for external feeds Not implemented let etag = None; // TODO.
x F59 Timezone parsing for external feeds Not implemented dtstart_tzid: None, // TODO: Parse timezone from ICS.

1.7 Alarms & notifications

B S D # Feature v1 status Notes
x F60 VALARM support in the model, add/edit/remove alarms in the form Works
x F61 Browser notification when an alarm fires Works
x F62 Alarm scheduler polling every 30s from the main app Works State in localStorage.
x F63 Service worker for "background" alarm processing Ceremony, no value The SW can't read localStorage, so it just pings the main tab, which is already polling. Deletable today with zero behavior change.
x F64 IndexedDB for persistent alarm storage Never built web-sys IndexedDB features + indexed_db_futures dep are enabled and shipped in the WASM bundle; zero code uses them.
x F65 Email/audio alarm actions Model only Only Display is handled.

1.8 Presentation & printing

B S D # Feature v1 status Notes
x F66 12 named color themes (Ocean, Forest, Sunset, Purple, Dark, Rose, Mint, Midnight, Charcoal, Nord, Dracula, Default) Works -- I want the concept of "color themes", but it doesn't need to be these 12. Some are good, some bad
x F67 3 layout "styles" (Default, Google, Apple), each a separate stylesheet Works 12 × 3 = 36 combinations to hand-maintain across ~8,300 lines of CSS. -- Again, I want the concept of "display styles", but it doesn't need to be implemented as it is currently, and we could do better than the ones we have
x F68 System dark-mode detection (prefers-color-scheme) Works
x F69 Print preview modal with live zoom, start/end hour clipping, per-element font-size and padding sliders Works 617 lines of Rust + 1,320 lines of print CSS, and it drives the interactive view components via extra print_* props. See §3.5. -- Implementation is bad here, I know, but this feature is important. I want to be able to print the calendars we're displaying
x F70 Actual printing Works Clones the DOM into a hidden node and measures it imperatively.
x F71 Mobile warning modal ("this app isn't for phones") Works Stands in for actually being responsive.
x F72 Responsive/mobile layout Not really F71 exists because F72 doesn't. -- We'll do it later, perhaps. CalDAV clients exist on Android already

1.9 Infrastructure

B S D # Feature v1 status Notes
x F73 Docker Compose stack (Axum backend + Caddy serving the WASM bundle) Works
x F74 SQLite + sqlx migrations, auto-run on container start Works
x F75 Gitea Actions workflow building & pushing a backend image Works
x F76 deploy_frontend.sh — rsync the dist/ to the server over SSH Works Frontend deploy is manual and separate from the backend's CI. -- I'm ok with making this automated instead
x F77 Backend integration test suite (18 tests) Does not compile Stale: calls AuthService::new(secret) (now takes 2 args) and builds AppState without its db field. Also duplicates the whole router instead of importing it.
x F78 Playwright E2E suite Source is gone 7.2 MB of stale HTML reports and trace zips remain on disk; not a single .spec.ts survives.
x F79 Unit tests 11 total 4 in dead code, 3 in config, 4 in the CalDAV client. -- Broadly speaking, I want this code being tested very thoroughly. That's my hedge against AI writing bad code

Part 2 — Stack & architecture decisions to re-litigate

Each of these worked. The question is whether you'd choose it again — Yes, same call; No, drop the approach entirely; or D for same intent, different execution (several of these are squarely in that third bucket — A3, A6 and A10 especially).

Y N D # Decision Verdict to consider
x A1 Rust + Yew + WASM frontend The 2.5 MB bundle, the 869 .clone() calls, and the lack of a mature component/CSS ecosystem are all downstream of this. It's a legitimate choice if you want one language end-to-end — but it is the reason the calendar viz was a headache. Worth an explicit yes/no. -- I'm a big proponent of Rust/WASM/functional programming. I prefer to do it this way
x A2 Axum backend as a CalDAV proxy Right call. Browsers can't do CalDAV directly (CORS, custom methods, credentials). Keep.
x A3 Cargo workspace with a shared calendar-models crate Right instinct, under-used — see §3.3. Keep the crate; actually route everything through it.
x A4 VEvent as an RFC 5545-faithful struct Genuinely good. The best asset in the repo. Carry it forward nearly as-is.
x A5 SQLite for users/sessions/prefs/feed cache Fine. Keep.
x A6 Password forwarded per-request rather than stored server-side Defensible (server never persists the secret), but it forced the password into localStorage. Consider an httpOnly cookie holding an encrypted credential blob instead. -- I'm a bit ambivalent. Open to whichever approach is best
x A7 Trunk for the frontend build Fine if A1 stays.
x A8 Client-side recurrence expansion Reconsider. Doing it server-side means one implementation, easier tests, smaller bundle, and the client just renders what it's given. -- Agree, just want to clarify that I do want the UI displaying recurring events as many events, like most calendars
A9 Fetch the whole calendar, filter client-side The CalDAV calendar-query REPORT supports a time-range filter and v1 never used it. Every view change refetches everything. -- ambivalent. whatever is best for performance
A10 Hand-written CSS with 553 custom properties The 116 !important declarations are the tell. Needs a real strategy (design tokens + a small utility layer, or a component library). -- open to whatever as long as we keep the "themes" and "styles" concepts

Part 3 — What actually went wrong

Concrete, with evidence. This is the part worth internalizing before starting over.

3.1 Reinventing libraries that exist

  • RRULE expansion, ~650 lines, hand-rolled. frontend/src/services/calendar_service.rs:331-1006expand_recurring_events, generate_occurrences, generate_weekly_byday_occurrences, generate_monthly_bymonthday_occurrences, generate_monthly_byday_occurrences, generate_yearly_bymonth_occurrences, parse_byday, add_months, add_years, days_in_month, is_leap_year. The rrule crate does all of this, RFC-compliant, with a test suite. days_in_month and is_leap_year are also already in chrono.

  • CalDAV XML parsing by regex. backend/src/calendar.rs:265extract_xml_content tries six regex patterns in sequence to cope with namespace prefixes, recompiling each Regex on every call inside a loop. quick-xml or roxmltree solves this properly and faster.

  • iCalendar serialization by string concatenation. backend/src/calendar.rs:1303-1546 — 240 lines of push_str(&format!(...)). No 75-octet line folding (an RFC violation that bites on long descriptions), no VTIMEZONE emission. The icalendar crate builds and folds correctly.

  • HTTP client, three times. 17 raw web_sys::RequestInit call sites across auth.rs, services/preferences.rs, and services/calendar_service.rs, each with its own header/error/JSON handling. gloo-net is one line per call.

  • Two near-identical datetime parsers (parse_datetime and parse_datetime_with_tz, backend/src/calendar.rs:715 and :807), plus a third copy in backend/src/handlers/ics_fetcher.rs:333. One of them loops over a formats array whose loop variable most branches never use — the same work runs three times per call.

3.2 Solving symptoms instead of causes

backend/src/handlers/ics_fetcher.rs:485-880 is ~500 lines named deduplicate_events, consolidate_same_title_events, deduplicate_exact_recurring_events, consolidate_weekly_patterns, would_event_be_generated_by_rrule, normalize_title, event_completeness_score.

It strips punctuation from event titles, lowercases them, groups by the result, guesses which of the collisions is "most complete", and throws the rest away. It will also try to merge two weekly RRULEs into one multi-day rule.

Almost certainly the real bug was upstream: the ICS parser doesn't handle RECURRENCE-ID overrides or VTIMEZONE (both are open TODOs in the same file), so a correct feed looked like it had duplicates. Rather than fix the parser, v1 added a lossy heuristic on top. Any two genuinely distinct events with similar titles and the same start time get silently merged.

Rule for v2: when output looks wrong, fix the parse, never post-process the output.

3.3 Four parallel representations of "an event"

  1. calendar_models::VEvent — the good, RFC-faithful one.
  2. backend::calendar::OldCalendarEvent — a deprecated flattened copy, still compiled (calendar.rs:18).
  3. CreateEventRequest / UpdateEventRequest / CreateEventSeriesRequest / UpdateEventSeriesRequest (backend/src/models.rs:100-200) — four near-identical stringly-typed wire structs. Dates and times as separate "YYYY-MM-DD" / "HH:MM" strings; attendees and categories as comma-separated strings; recurrence decomposed into recurrence: String + recurrence_days: Vec<bool>
    • interval + count + end_date instead of just… the RRULE that VEvent already holds.
  4. EventCreationData (frontend/src/components/event_form/types.rs:64) — the form model, with its own EventStatus and EventClass enums duplicating the ones in calendar-models, converted to the wire format via a 22-element tuple (to_create_event_params) that serializes enums with format!("{:?}", …).to_uppercase().

Plus a fifth, frontend::calendar::CalendarEvent, in dead code (see §3.6).

Every conversion between these is a place to lose data, and several of them do — that's how monthly_by_day (F39) ends up in the form but never reaches the server.

Rule for v2: VEvent is the wire format. POST /events takes a VEvent. No parallel DTOs.

3.4 localStorage as a global mutable variable

caldav_credentials (the plaintext CalDAV password) is read directly from localStorage in 8 separate places inside app.rs alone, plus components/calendar.rs. Preferences live in localStorage and in the SQLite user_preferences table with no defined source of truth — some keys are written to both, some to one. calendar_view_mode, calendar_theme, calendar_style, calendar_selected_date, calendar_time_increment, calendar_colors, last_used_calendar, user_preferences (a JSON blob of the others) all coexist.

This is the classic "one hidden global" pattern: any component can read or write app state without declaring that it does, so nothing is traceable and cache-invalidation bugs are inevitable (commit 933d7a8 Fix calendar visibility preservation during event updates is one of these).

Rule for v2: one auth context, one preferences store, one writer. Secrets never in localStorage.

3.5 The 2,008-line App component

frontend/src/app.rs is a single #[function_component] holding 35 use_state hooks and 265 .clone() calls. Every modal's open/closed flag, every context menu's x/y position, the color palette, the alarm scheduler, the refresh interval — all in one function, all prop-drilled down through components that only pass them along.

Symptoms this produced:

  • CalendarProps.on_event_update_request is a callback taking a 7-element tuple.
  • A timestamp field on UserInfo whose only job is // Add timestamp to force re-render.
  • print_mode, print_pixels_per_hour, print_start_hour props threaded into WeekView so the print-preview modal can reuse it — print concerns leaking into the interactive view.
  • The print modal doing imperative DOM surgery (set_inner_html, cloning nodes, measuring client_height, mutating inline styles, query_selector_all(".week-event")) inside a declarative framework — print_preview_modal.rs:244-303.

Rule for v2: state lives at the level that owns it. Use a reducer + context for genuinely global state. If a prop is a tuple of more than two things, it wants to be a struct.

3.6 Dead code shipped to users

  • frontend/src/calendar.rs638 lines: a complete second CalDAV client (its own CalendarEvent, CalDAVClient, XML extraction, iCal parsing, 4 unit tests) that is not declared in main.rs and has never been reachable.
  • Because that file looks used, reqwest, ical, and regex stay in the frontend's dependency list and get compiled into the WASM bundle.
  • Also fully unused frontend deps: chrono-tz, indexed_db_futures, base64, dotenvy, serde-wasm-bindgen, plus the 8 IndexedDB web-sys features.
  • OldCalendarEvent, EventReminder, ReminderAction in backend/src/calendar.rs — superseded, still compiled.
  • backend/src/debug_caldav.rs — a debug scratch module.
  • Duplicate top-level migrations/ directory shadowing the real backend/migrations/.
  • Three orphaned _recurring_edit_* state hooks in app.rs:140-142.

A meaningful share of that 2.5 MB bundle is code nobody can execute.

3.7 Observability and error handling

  • 157 println! calls in the backend with emoji prefixes (🔄, , 🗑️); zero uses of log or tracing. No levels, no filtering, no structure — and a login handler that prints Password length: {}.
  • 74 web_sys::console calls in the frontend, same story.
  • 65 .unwrap() calls across both crates.
  • Errors are String at nearly every frontend boundary (Result<T, String>), so nothing can be matched on — a 401 and a JSON parse failure are indistinguishable to the caller.
  • Handlers dispatch on stringly-typed actions ("delete_this", "this_and_future", "all_in_series", "update_series") — 53 occurrences — instead of enums, so a typo is a runtime fallthrough rather than a compile error.

3.8 Timezone modelling

The client computes its current UTC offset (format!("{:+03}:{:02}", …) from js_sys::Date::get_timezone_offset()) and sends that as timezone: String on every request. An offset is not a timezone: -06:00 in January and -06:00 in July are different zones, and a recurring 9am meeting created in winter drifts an hour after the DST boundary. The VEvent model already has proper *_tzid fields; the wire format throws them away in favour of an offset. chrono-tz is in the frontend's dependency list and never used.

Rule for v2: IANA TZID everywhere (America/Denver), never a raw offset. Store the zone, derive the offset.

3.9 CSS at scale, without a system

  • styles.css: 5,610 lines. print-preview.css: 1,320 lines. styles/google.css: 19 KB. styles/apple.css: 15 KB.
  • 553 CSS custom property definitions, 116 !important declarations.
  • Selectors are redefined many times over — .external-calendar-modal appears 23 times, .login-form 18, .calendar-day 13, .week-event 12.
  • Font Awesome pulled from a CDN in index.html (an external dependency on every page load).
  • The last ~15 commits in the repo are almost entirely CSS-tweak commits for the print preview.

The 36 theme × style combinations were never worth their maintenance cost on a personal app.

3.10 Testing and correctness feedback

Effectively no safety net. 11 unit tests total (4 of them in dead code). The integration suite doesn't compile against current signatures. The Playwright specs were deleted but 7.2 MB of their reports remain. Every regression was found by using the app.

The recurring-event edit semantics (§F41-F44) are genuinely subtle — EXDATE + RECURRENCE-ID + UNTIL splitting — and they were shipped with zero automated coverage.


Part 4 — What v2 should reach for instead

No columns here — these follow from the decisions above rather than being decisions themselves.

v1 hand-rolled v2 candidate
650-line RRULE expander rrule crate
Regex XML extraction quick-xml or roxmltree
String-concat iCal writer icalendar crate (handles line folding)
3 copies of a datetime parser one function in calendar-models, tested
17 raw RequestInit fetch sites gloo-net (or a single typed API client module)
println! with emoji tracing + tracing-subscriber
Result<T, String> thiserror enums, both crates
Stringly-typed actions #[derive(Serialize, Deserialize)] enum EditScope { ThisOnly, ThisAndFuture, EntireSeries }
Offset-as-timezone chrono-tz with IANA TZIDs
8,300 lines of bespoke CSS a small token set + one utility layer; ship one theme with a light/dark pair
Manual visual verification headless-browser screenshots in the loop (see below)

Verifying the calendar viz this time

The thing that made week view painful — pixel math you can't see until you build, deploy, and squint — is now directly addressable. I can drive a real browser, take screenshots of the running app, and compare them as I go. That means for v2 we can:

  • screenshot the week grid at each step and check the layout by eye before moving on;
  • build a small fixture set (overlapping events, all-day spans, DST-boundary days, midnight-crossing events, a 6-week month) and snapshot each one;
  • catch layout regressions the moment they appear rather than three commits later;
  • read the browser console for panics and warnings without you having to reproduce them.

This is the single biggest change in what's possible between v1 and v2, and it should shape the plan: build the view layer against real rendered output from day one.


Part 5 — Open questions for you

Answer inline underneath each — free text, no columns.

  1. Rust/WASM again, or a JS/TS frontend? (A1) Everything about the CSS pain, the bundle size, and the viz difficulty traces back here. Keeping Rust is defensible; it should just be a decision rather than an inheritance.

    Keep it as Rust/WASM. This is important to me. I'm a big fan.

  2. Single-user or multi-user? (F8) If it's only ever you, the users table, JWT, and half the session machinery evaporate.

    Multi-user. I'd like this to eventually be a usable tool by households where more than one person might want to use it.

  3. Which views do you actually use? Month and week both? Would a day or agenda view earn its keep?

    The other views are also nice to have. But I mostly use week and month. I do want them all though, I suppose.

  4. Themes: how many? One good light/dark pair, or is the 12-theme picker something you use?

    It doesn't have to be 12, but I like having themes. I'm sure we can come up with a clever way to make this not too complex.

  5. Print preview: keep? It was ~2,000 lines and the last 15 commits. Is printing a real workflow or a weekend detour?

    Yes, printing is important. I want to be able to print one of these calendars with the currently visible items in a way that is pretty, consistent with the current theme, takes up the whole page and nothing more, etc so I can laminate it and put it on my fridge.

  6. Alarms: keep? Browser notifications only fire when the tab is open. Your phone already does this from the CalDAV server. Is the web client the right place for it?

    The alarms are important to make it to the server (my phone client will use them and notify me). Does the browser need to notify you when they go off? I mean, it's not critical, but I don't think it's that difficult and it's nice to have.

  7. External ICS feeds: keep? Worth it, but only if we do the parse correctly (§3.2) instead of the dedup heuristics.

    Yes, these are important. In particular outlook, so I can put my work schedule on there. But you're right, we should handle them correctly.

  8. Drag-and-drop editing: how important? It's the highest-risk UI feature per unit of value.

    It's important. I really want a snazzy UX for this. Drag and drop for simple moves AND the context menu for "full editing"

  9. Anything in v1 you actively miss or that never worked right that isn't listed above?