Skip to content

feat(planner): name a saved dive plan on first save#722

Open
ericgriffin wants to merge 7 commits into
mainfrom
worktree-name-saved-dive-plan
Open

feat(planner): name a saved dive plan on first save#722
ericgriffin wants to merge 7 commits into
mainfrom
worktree-name-saved-dive-plan

Conversation

@ericgriffin

Copy link
Copy Markdown
Member

Problem

Saving a dive plan was silent, so every new plan kept the hardcoded, non-localized default New Dive Plan. A diver who saved three plans got three identical rows in the saved-plans sheet, separable only by the date/depth subtitle. Renaming was possible but undiscoverable: the canvas AppBar title was a bare InkWell with no affordance suggesting it could be tapped.

What changed

Prompt on first save. The first time a never-persisted plan is saved, a dialog asks for a name, pre-filled with a generated default. Cancel aborts the save entirely, so nothing is written and the plan stays dirty. Re-saves stay silent.

Generated default name. Blue Hole 40m - Jul 25, composed from the plan's site, its unit-formatted max depth, and its start date. Absent parts drop out (Blue Hole - Jul 25, 40m - Jul 25), and when neither a site nor a depth is available it falls back to Dive Plan - Jul 25. An empty plan never produces 0m.

Discoverable rename. The canvas title gains a small pencil icon, and the saved-plans overflow menu gains a Rename entry alongside Duplicate and Share.

Also fixes a real leak: the old inline rename dialog created a TextEditingController inside its builder closure and never disposed it. The extracted _PlanNameDialog is a StatefulWidget that disposes it properly.

Scope

Presentation layer only. dive_plans.name already existed as a non-null column carrying an hlc and was already registered in the sync table map, so a rename propagates through the existing last-writer-wins path. No migration, no schema-version bump, no sync change — verified by git diff main --stat -- lib/core/database/ returning empty.

Design decisions

Decision Choice
When to prompt First save only
Cancel semantics Aborts the save; the dialog is a gate, not a courtesy
Other save paths Convert-to-Dive, import, duplicate, and undo-restore stay unprompted
Name collisions Allowed; names are labels, not identifiers, and uniqueness is unenforceable across synced devices

The generated name embeds a unit-formatted depth, which freezes that unit into the stored string. That is deliberate: the name is a user-editable label, and regenerating it after a unit switch would overwrite names the diver typed.

One accepted consequence of not gating Convert-to-Dive: converting a never-persisted plan still writes a row named New Dive Plan. That is recoverable through the new Rename entry.

Testing

  • 6 unit tests for generateDefaultPlanName covering all four presence combinations, blank-site handling, and whitespace trimming
  • 5 widget tests for the dialog: pre-fill, cancel-returns-null, trimmed confirm, whitespace-disables-confirm, and re-enable
  • 4 widget tests for the first-save gate: dialog opens with the generated default, cancel persists nothing and leaves the plan dirty, confirm persists under the entered name, and a second save does not re-prompt
  • 1 widget test for rename from the overflow menu
  • One pre-existing test (save action clears the dirty flag) was updated to confirm the dialog, since the first-save contract deliberately changed

Full suite: 14,051 passing, 14 skipped. flutter analyze clean, dart format . clean.

Localization

Two new keys (plannerCanvas_name_dialogTitle, plannerCanvas_name_defaultFallback) added to app_en.arb and all ten non-English locales, then regenerated.

Docs

Design spec and implementation plan are committed under docs/superpowers/.

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

This PR improves the dive planner UX by prompting for a meaningful plan name on the first save (instead of persisting multiple indistinguishable “New Dive Plan” rows), adds a reusable name-entry dialog (fixing a TextEditingController lifecycle leak), and makes renaming more discoverable from both the canvas and the saved-plans sheet.

Changes:

  • Gate first-time plan persistence behind a localized name dialog seeded with a generated “site + depth + date” default (unit-formatted depth, locale-aware date).
  • Extract a reusable plan-name dialog widget (properly disposing its TextEditingController) and add rename affordances (pencil icon in canvas title, Rename menu item in saved plans).
  • Add comprehensive unit/widget tests and new l10n keys across all locales (regenerated localizations).

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/features/planner/saved_plans_sheet_test.dart Pins locale to English for string-based finders; adds overflow-menu rename test.
test/features/planner/plan_name_generator_test.dart Adds unit tests for the default plan-name generator (note: needs Intl.defaultLocale pinning for determinism).
test/features/planner/plan_name_dialog_test.dart Adds widget tests validating dialog contract (prefill, cancel/null, trim, disable/enable confirm).
test/features/planner/plan_canvas_page_test.dart Updates existing save test to confirm the new first-save name dialog.
test/features/planner/plan_canvas_first_save_test.dart Adds widget coverage for first-save gating behavior (open, cancel aborts, confirm persists, second save no reprompt).
lib/l10n/arb/app_ar.arb Adds planner name-dialog title + fallback label strings (Arabic).
lib/l10n/arb/app_de.arb Adds planner name-dialog title + fallback label strings (German).
lib/l10n/arb/app_en.arb Adds planner name-dialog title + fallback label strings (English source).
lib/l10n/arb/app_es.arb Adds planner name-dialog title + fallback label strings (Spanish).
lib/l10n/arb/app_fr.arb Adds planner name-dialog title + fallback label strings (French).
lib/l10n/arb/app_he.arb Adds planner name-dialog title + fallback label strings (Hebrew).
lib/l10n/arb/app_hu.arb Adds planner name-dialog title + fallback label strings (Hungarian).
lib/l10n/arb/app_it.arb Adds planner name-dialog title + fallback label strings (Italian).
lib/l10n/arb/app_nl.arb Adds planner name-dialog title + fallback label strings (Dutch).
lib/l10n/arb/app_pt.arb Adds planner name-dialog title + fallback label strings (Portuguese).
lib/l10n/arb/app_zh.arb Adds planner name-dialog title + fallback label strings (Chinese).
lib/l10n/arb/app_localizations.dart Regenerates base localization interface with new getters.
lib/l10n/arb/app_localizations_ar.dart Regenerates Arabic localizations for new planner keys.
lib/l10n/arb/app_localizations_de.dart Regenerates German localizations for new planner keys.
lib/l10n/arb/app_localizations_en.dart Regenerates English localizations for new planner keys.
lib/l10n/arb/app_localizations_es.dart Regenerates Spanish localizations for new planner keys.
lib/l10n/arb/app_localizations_fr.dart Regenerates French localizations for new planner keys.
lib/l10n/arb/app_localizations_he.dart Regenerates Hebrew localizations for new planner keys.
lib/l10n/arb/app_localizations_hu.dart Regenerates Hungarian localizations for new planner keys.
lib/l10n/arb/app_localizations_it.dart Regenerates Italian localizations for new planner keys.
lib/l10n/arb/app_localizations_nl.dart Regenerates Dutch localizations for new planner keys.
lib/l10n/arb/app_localizations_pt.dart Regenerates Portuguese localizations for new planner keys.
lib/l10n/arb/app_localizations_zh.dart Regenerates Chinese localizations for new planner keys.
lib/features/planner/presentation/widgets/saved_plans_sheet.dart Adds Rename action to saved-plan overflow menu, wired to the shared dialog.
lib/features/planner/presentation/widgets/plan_name_dialog.dart New reusable plan-name dialog widget with proper controller disposal + validation.
lib/features/planner/presentation/pages/plan_canvas_page.dart Adds title edit affordance; gates first save on dialog; reuses shared rename dialog.
lib/features/planner/domain/services/plan_name_generator.dart New pure generator for default plan name (site/depth/date/fallback).
lib/features/dive_planner/presentation/providers/dive_planner_providers.dart Exposes isPersisted to support “first save only” prompting logic.
docs/superpowers/specs/2026-07-25-name-saved-dive-plan-design.md Adds design spec documenting UX, architecture, and rationale.
docs/superpowers/plans/2026-07-25-name-saved-dive-plan.md Adds detailed implementation plan and verification checklist.

Comment on lines +1 to +8
import 'package:flutter_test/flutter_test.dart';
import 'package:submersion/features/planner/domain/services/plan_name_generator.dart';

void main() {
final date = DateTime(2026, 7, 25);

group('generateDefaultPlanName', () {
test('combines site, depth, and date', () {
@github-actions

Copy link
Copy Markdown
Contributor

📦 Build artifacts for this PR · commit 879da9a

Platform Download
Android (APK) android-apk
macOS macos-build
Windows windows-build
Linux linux-build

Artifacts expire in 7 days. Downloading requires being signed in to GitHub. The macOS build is ad-hoc signed — right-click → Open on first launch.

Updated automatically on each push.

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.55172% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...s/planner/presentation/pages/plan_canvas_page.dart 94.44% 2 Missing ⚠️
...planner/presentation/widgets/plan_name_dialog.dart 96.87% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

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

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants