Skip to content

[ASNT-15] Project-creation wizard fixes + Google Sheet import (cloud-sync) - #20

Merged
devnull03 merged 8 commits into
mainfrom
project-creation-wizard
Jul 12, 2026
Merged

devnull03 merged 8 commits into
mainfrom
project-creation-wizard

Conversation

@devnull03

Copy link
Copy Markdown
Owner

Builds on the project-creation wizard (ASNT-15) with a round of fixes and a new cloud-sync crate for Google Sheet import (ASNT-17).

Wizard fixes

  • Blank projects now flow through the Files and Columns steps (folder picker + a "skip columns for now" card) instead of skipping them.
  • Skip-files toggle — "I'll add a files folder later" also skips the Link step (skip_files / skips_link).
  • Link pattern input moved inside its option card, revealed on select, rather than floating outside it.
  • Fixed a gpui panic — cannot read ProjectWizard while it is already being updated when clicking any Columns option other than Auto. The recent/load-config dialogs now open via window.defer, outside the click listener's own entity update.

cloud-sync crate (Google Sheet import)

  • fetch_sheet(link) imports a public ("anyone with the link") sheet via the built-in ?format=xlsx export — no OAuth, no Sheets API v4. One download yields both the first tab's rows (via calamine) and its cell notes (zip + quick-xml over xl/comments*.xml). Private sheets get a clear error.
  • Threaded comments aren't reachable via export (they'd need Drive API + OAuth) — deferred.
  • The legacy CSV-export path is kept as marked dead code until the wizard is rewired onto fetch_sheet (tracked as a follow-up task).

Verification

cargo fmt --check, cargo clippy -D warnings, cargo build, and cargo test -p cloud-sync all clean on the affected crates.

🤖 Generated with Claude Code

claude and others added 3 commits July 11, 2026 13:19
…ect flow

Implements the Claude Design handoff "Qrate Wizard Flow": a Photoshop-style
startup launcher (Recent Projects + Create New cards for Blank / CSV+folder /
Google Sheet), and a New Project wizard window with Name, Files (CSV or Sheet
fork), Link-files, Columns (with recently-used and load-from-file/Sheet
sub-dialogs), Review, and Success steps.

- New crates/project-wizard crate: wizard shell + per-step render impls,
  real CSV parsing (csv crate) and folder matching validated against
  sample/, column-config CSV loading with the design's error variants,
  recent-projects persistence through AppSettings, and project.json
  manifest written on create. Google Sheets validation is mocked pending
  a real Sheets API integration (documented in data.rs).
- Launcher replaces the ASNT-13 debug startup window; crates/app/src/debug.rs
  and its call sites are removed as its comments instructed.
- NewProject action wired to the wizard (menu item + existing ctrl-n binding).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LNjLXbAcnnHeTZvk3gbvhh
New minimal crate for importing a public ("anyone with the link") Google
Sheet, no OAuth or Sheets API v4. `fetch_sheet` pulls the built-in
`?format=xlsx` export once and derives both the first tab's rows (via
calamine) and its cell notes (zip + quick-xml over `xl/comments*.xml`).
A private sheet serves an HTML sign-in page and yields a clear error.

Threaded comments aren't reachable this way — they survive no export and
would need the Drive API + OAuth; deferred. The older CSV-export path is
kept as marked-legacy dead code until the wizard is rewired onto
`fetch_sheet`.
- Blank projects now flow through the Files and Columns steps (folder
  picker + a "skip columns for now" card) instead of jumping past them.
- Add an "I'll add a files folder later" toggle that also skips the Link
  step (skip_files / skips_link).
- Move the custom-pattern input inside its Link option card, revealed on
  select, rather than floating outside it.
- Fix a gpui panic ("cannot read ProjectWizard while it is already being
  updated") — the recent/load-config dialogs now open via window.defer,
  outside the click listener's own entity update.
- Wire the Google Sheet entry path to cloud-sync for real fetching, and
  drop the now-unused success step.
Copilot AI review requested due to automatic review settings July 12, 2026 10:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a full “New Project” launcher + multi-step project-creation wizard, and introduces a new cloud-sync crate for importing public Google Sheets (CSV today, XLSX+notes groundwork), wiring the app to start on the launcher and adding a “New Project…” menu action.

Changes:

  • Introduces project-wizard crate (launcher, wizard steps, CSV parsing, folder matching, recent projects, and project.json manifest creation).
  • Introduces cloud-sync crate for fetching public Google Sheets (CSV export + XLSX parsing for rows/notes).
  • Updates app startup/menu/actions and workspace members (removes debug scaffolding; launcher becomes the real startup window).

Reviewed changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
crates/project-wizard/src/wizard.rs Core wizard state machine, navigation, and shared UI scaffolding.
crates/project-wizard/src/steps/mod.rs Registers wizard step modules.
crates/project-wizard/src/steps/name.rs Name step UI + live validation.
crates/project-wizard/src/steps/files.rs Files step variants (CSV/Sheet/Blank) + folder matching + sheet check.
crates/project-wizard/src/steps/link.rs Link step UI including advanced pattern input within option card.
crates/project-wizard/src/steps/columns.rs Columns step UI + dialogs for recent/load config; sheet-config fetch path.
crates/project-wizard/src/steps/review.rs Review + “Create Project” manifest write and handoff to main window.
crates/project-wizard/src/data.rs CSV preview parsing, folder matching, and column-config loading (+ tests).
crates/project-wizard/src/recent.rs Persisted recent-project list and relative-time formatting.
crates/project-wizard/src/project.rs Writes on-disk project.json manifest and project directory creation.
crates/project-wizard/src/launcher.rs Startup launcher window (recents + create-new cards).
crates/project-wizard/src/lib.rs Crate entry points and exports.
crates/project-wizard/Cargo.toml New crate manifest + dependencies (incl. cloud-sync).
crates/cloud-sync/src/lib.rs Google Sheet fetch (CSV/XLSX) + XLSX notes parsing (+ tests).
crates/cloud-sync/Cargo.toml New crate manifest + deps (reqwest, calamine, zip, quick-xml).
crates/app/src/main.rs Makes launcher the startup window; adds LauncherHooks; registers New Project action.
crates/app/src/debug.rs Removes prior debug scaffolding window/actions.
crates/app/src/app_menus.rs Replaces debug menu item with “New Project…”.
crates/app/src/actions.rs Updates handler documentation (NewProject now registered in main.rs).
crates/app/Cargo.toml Adds dependency on project-wizard.
Cargo.toml Adds new workspace members and adds csv to workspace deps.
Cargo.lock Updates lockfile for new crates/dependencies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +20 to +27
let files_matched = self
.folder_match
.as_ref()
.map(|m| (m.matched_rows, m.total_rows));
let link_method = (self.entry_kind != EntryKind::Blank).then(|| match self.link_method {
LinkMethod::ExactFilename => "exact filename".to_string(),
LinkMethod::CustomPattern => "custom pattern".to_string(),
});
Comment on lines +97 to +111
let files_line = self.folder_match.as_ref().map(|m| {
let method = match self.link_method {
LinkMethod::ExactFilename => "exact filename",
LinkMethod::CustomPattern => "custom pattern",
};
let extra = if m.extra_files > 0 {
format!(" · {} not linked", m.extra_files)
} else {
String::new()
};
format!(
"{} of {} matched ({method}){extra}",
m.matched_rows, m.total_rows
)
});
Comment on lines +152 to +154
.when(self.entry_kind != EntryKind::Blank, |el| {
el.child(summary_row("Columns", &columns_line))
}),
Comment on lines +74 to +86
if delta < 60 {
"just now".into()
} else if delta < 3600 {
format!("{} minutes ago", delta / 60)
} else if delta < 86_400 {
format!("{} hours ago", delta / 3600)
} else if delta < 86_400 * 7 {
format!("{} days ago", delta / 86_400)
} else if delta < 86_400 * 30 {
format!("{} weeks ago", delta / (86_400 * 7))
} else {
format!("{} months ago", delta / (86_400 * 30))
}
Comment on lines +112 to +127
let dir = std::env::temp_dir().join("qrate-sheets");
fs::create_dir_all(&dir)?;
let path = dir.join(format!("{id}.csv"));
fs::write(&path, &body)?;
Ok(path)
}

/// Where a fetched sheet lands, without fetching — handy for callers/tests.
pub fn cache_path_for(link: &str) -> Option<PathBuf> {
let SheetRef { id, .. } = parse_sheet_ref(link).ok()?;
Some(
std::env::temp_dir()
.join("qrate-sheets")
.join(format!("{id}.csv")),
)
}
url.push_str(&format!("&gid={gid}"));
}

let resp = reqwest::blocking::get(&url)?.error_for_status()?;
Comment thread crates/cloud-sync/src/lib.rs Outdated
pub fn fetch_sheet(link: &str) -> Result<SheetData, SheetSyncError> {
let SheetRef { id, .. } = parse_sheet_ref(link)?;
let url = format!("https://docs.google.com/spreadsheets/d/{id}/export?format=xlsx");
let resp = reqwest::blocking::get(&url)?.error_for_status()?;
LinkMethod::CustomPattern => "custom pattern".to_string(),
});
let columns = match self.entry_kind {
EntryKind::Blank => Vec::new(),
Comment on lines +59 to +67
// ponytail: blocking fetch on the UI thread — fine for a "Check" click;
// move to the background executor if it ever drags.
fn load_config_from_sheet(&mut self, cx: &mut Context<Self>) {
let link = self.sheet_link_input.read(cx).value().to_string();
let fetched = cloud_sync::fetch_sheet_csv(&link)
.map_err(|e| e.message())
.and_then(|path| {
data::load_csv_preview(&path.to_string_lossy()).map_err(|e| e.message())
});
Unformatted since ec7d4ad; caught by `cargo fmt --all --check` (the CI gate).
Address Copilot review on PR #20:
- When "add files later" is checked, the folder/link state is stale, so
  don't populate files_matched / link_method or show a Files line that
  claims a match happened.
- Blank projects now flow through the Columns step, so show the Columns
  row in review and keep any config they loaded there instead of forcing
  an empty column list.
- Pluralize relative_time ("1 minute ago", not "1 minutes ago").
Address Copilot review on PR #20: a bare blocking request has no timeout,
so a stalled connection hangs the background worker forever. Use a client
with a 30s timeout so it errors out instead.
@devnull03

Copy link
Copy Markdown
Owner Author

Audited the Copilot review — pushed 6787eca..966f98e.

Applied (5):

  • skip-files leaves stale state (review.rs:27, :111) — when "add files later" is checked, files_matched/link_method and the review "Files" line no longer claim a match happened.
  • Blank hides Columns / discards config (review.rs:154, :29) — blank now goes through Columns, so the review shows the Columns row and create_project keeps a config loaded there instead of forcing an empty list.
  • relative_time plural (recent.rs:86) — now "1 minute ago".
  • No fetch timeout (lib.rs:154) — fetch_sheet uses a client with a 30s timeout so a stalled connection errors instead of hanging the background worker.

Dismissed (4):

  • gid cache collision (lib.rs:127) — scope is first-tab-only (distinct spreadsheet id per file); flagged on the legacy CSV path that's slated for removal. Not a bug in current scope.
  • Timeout on fetch_sheet_csv (lib.rs:89) — legacy/dead path being deleted once the wizard is rewired onto fetch_sheet.
  • Blocking fetch in load_config_from_sheet (columns.rs:67) — deliberate, ponytail:-marked simplification for a user-initiated "Check" click; the new fetch timeout bounds its worst case. Full async refactor deferred to the wiring task.

Also ran the full CI suite locally before pushing (cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings -A dead_code, cargo test --workspace, cargo build --workspace) — all green.

Runs `cargo fmt --all --check` (CI's fmt gate) on every commit so
unformatted code can't land — the check that missed ec7d4ad. Shared via
core.hooksPath; enable per clone with `git config core.hooksPath .githooks`.
`cargo clippy --all-targets` and `cargo test --workspace` already compile
every target (incl. non-test bin code), so a separate `cargo build` adds
nothing but CI time.
@devnull03
devnull03 merged commit 4e367a6 into main Jul 12, 2026
2 checks passed
devnull03 added a commit that referenced this pull request Jul 12, 2026
Address Copilot review on PR #20:
- When "add files later" is checked, the folder/link state is stale, so
  don't populate files_matched / link_method or show a Files line that
  claims a match happened.
- Blank projects now flow through the Columns step, so show the Columns
  row in review and keep any config they loaded there instead of forcing
  an empty column list.
- Pluralize relative_time ("1 minute ago", not "1 minutes ago").
devnull03 added a commit that referenced this pull request Jul 12, 2026
Address Copilot review on PR #20: a bare blocking request has no timeout,
so a stalled connection hangs the background worker forever. Use a client
with a 30s timeout so it errors out instead.
@devnull03
devnull03 deleted the project-creation-wizard branch July 12, 2026 17:47
@devnull03 devnull03 changed the title Project-creation wizard fixes + Google Sheet import (cloud-sync) [ASNT-15] Project-creation wizard fixes + Google Sheet import (cloud-sync) Jul 13, 2026
@notion-workspace

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants