Skip to content

Add detail view and create/finish time for task - #27

Open
LiuYinCarl wants to merge 60 commits into
GabAlpha:masterfrom
LiuYinCarl:master
Open

LiuYinCarl wants to merge 60 commits into
GabAlpha:masterfrom
LiuYinCarl:master

Conversation

@LiuYinCarl

Copy link
Copy Markdown
  1. Add Create time and Finsih time for tasks.
  2. Add Task Detail View which binding to 'v'.
image

LiuYinCarl and others added 23 commits January 19, 2026 22:07
Completed tasks are now hidden in the task list by default. Press `t`
to toggle their visibility and immediately refresh the view.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
- Remove unused `toml` dependency
- Fix `get_indicator_done_tasks_color` 50% boundary overlap bug
- Move `load_delete_confirm_items` from task.rs to ui.rs
- Extract `format_timestamp`/`format_duration` to util.rs
- Eliminate clone-write-reload anti-pattern in all CRUD operations
- Extract `handle_modal_nav` helper for repeated modal keyboard logic
- Remove dead `reload` and `_get_all` methods

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
- Add TASK_PRIORITY_NONE constant instead of magic 0
- Widen status and priority list modals to prevent text clipping
- Update all priority 0 references to TASK_PRIORITY_NONE

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
- AddProject handler selected projects.len() (one past the end), so
  opening the tasks of a freshly created project panicked with an
  index out of bounds error; select len() - 1 and skip empty titles
- Guard App::next/previous against empty lists (len() - 1 underflow)
- Allow overriding the storage dir via BASILK_CONFIG_DIR so tests
  never touch the real config directory
- Add unit tests for util, migration, json, project, task and app
  navigation, plus proptest-based property/fuzz tests
- Stopwatch (s) bound to the selected task accumulates into
  time_spent_secs; pomodoro (c) is a global countdown that rings the
  terminal bell at zero and persists nothing
- Per-task estimated_hours (g in task details) with progress shown in
  the task list, details view, and timer modal
- Event loop switches to poll-based ticking for live readouts; timers
  survive view switches and settle on stop, countdown zero, or quit
- New c8f21 JSON migration for the timer fields
The timer modal now shows the readout as 5-row block-art digits instead
of a single text line, and a finished pomodoro rings the bell once and
stays visible ("time's up!") until dismissed with any key instead of
closing automatically.
Press b in the task view to toggle between the classic list and a
board with Up Next / On Going / Done swimlanes. Left/right switch
lanes, up/down move within a lane; all task keybindings (details,
status, priority, timers, ...) work unchanged since the board is a
rendering mode of ViewTasks, not a new ViewMode. The board always
shows the Done lane and the focus follows a task across lanes when
its status changes.

Task lines are built by the shared Task::repr_spans helper so the
list and board renderings cannot drift apart.
Split App::run's monolithic per-view key handling into dedicated
handlers so the loop stays shallow, and break Json::check into
focused paths for the current data file and legacy versioned files.
Press m in the project list to open global, project-independent notes.
A note is a titled entry whose body is Markdown: Enter opens a
full-page rendered preview (pulldown-cmark -> ratatui Text with a
style stack), and e switches to a full-page tui-textarea editor
(Esc saves and returns to the preview).

Notes are stored in the same basilk_data.json wrapper behind
#[serde(default)], so pre-notes files load unchanged; the e5a1c
migration only bumps the version. Both write paths preserve the
other half of the file.

Also fix show_items classifying ViewHelp/InfoMigration: opening help
over the projects or notes view with an empty project list fell into
the tasks branch and panicked in Project::get_current (ratatui clears
the list selection when rendering an empty list).
fanitriastowo added a commit to fanitriastowo/basilk that referenced this pull request Aug 25, 2026
…, board, timers

Pulls LiuYinCarl/basilk master (upstream GabAlpha/basilk PR GabAlpha#27, unmerged
there) into this fork: task detail view with create/finish times, per-task
notes, global Markdown notes, kanban board view, stopwatch and pomodoro
timers, time estimates, hide-done toggle, help popup, and the first test
suite in the repo.

Data moves from per-version <version>.json files to a single
basilk_data.json wrapper, migrated on first launch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xmQhyWJSB8Lak4GTto1Fp
LiuYinCarl and others added 3 commits August 25, 2026 20:48
Automates quality checks on every push and pull request so
regressions are caught before review.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
Resolves redundant returns, slice and string parameter types, and
other lints so clippy can gate future changes with -D warnings.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-pro
Raise line coverage from ~58% to 99.5% (only the real-terminal glue in
main.rs stays uncovered, since it cannot run inside a unit test):

- Refactor the event loop into run_with_source over an injectable
  KeySource so tests drive the full draw/dispatch/tick loop in-process
  with synthetic key events (Quit, Skip, migration info, Release filter).
- Make Cli::parse pure and unit-testable; --version print/exit moved to
  main and verified end-to-end by tests/cli.rs via the real binary.
- Cover every key handler, all 23 ViewModes, the board view, timer and
  hint readouts, delete/help/details/input modals, markdown edge cases
  (HTML, images, H3, hard breaks, trailing blank trim), JSON config-dir
  fallback and current-version check, and digit glyphs.
- Fix a latent panic: opening help over a view with an empty list used
  to clear selected_project_index (use_state(ViewHelp) always returned
  the project state, and ratatui resets the state of an empty list),
  panicking in Project::get_current on the next redraw. ViewHelp now
  maps to the list state of the view it was opened from.
- Enforce coverage in CI with cargo llvm-cov --fail-under-lines 95.
kenshin added 2 commits August 26, 2026 00:13
checkout@v4/v3 and cache@v4 target Node 20, which GitHub now runs on
a deprecated runtime (forced onto Node 24). checkout@v5 and cache@v5
use node24 natively with no usage changes.
- release.yml: any push to master touching src/, tests/, Cargo.toml or
  Cargo.lock bumps the patch version (commit subjects starting with
  [major]/[minor] bump that component instead), tags vX.Y.Z, builds
  release binaries for Linux, macOS (Intel + Apple Silicon) and Windows,
  and publishes them to a GitHub Release with auto-generated notes.
  A concurrency queue prevents racing bumps; the bump commit is pushed
  with GITHUB_TOKEN so it cannot re-trigger the workflow.
- scripts/bump-version.sh: local manual bump (patch/minor/major) that
  updates Cargo.toml + Cargo.lock, commits and tags.
- Document the versioning/release mechanism in README and AGENTS.md.
kenshin and others added 30 commits August 26, 2026 00:51
download-artifact merge-multiple flattened the three unix binaries,
all named 'basilk', so only one survived. Stage each binary as
basilk-<target> (and basilk-<target>.exe on Windows) before upload.
scaling of Task/Project::load_items, full-frame render at 2k tasks
(list and board), Markdown rendering and JSON persistence, run via
./scripts/bench.sh. Measured on release build: ~0.3 ms/frame at 2k
tasks (250 ms budget), ~2.5 ms for 10k-task load_items, 312 KB
Markdown in ~3.5 ms, idle CPU 0.0%, startup ~6 ms.
The workflow no longer bumps versions or commits on its own. Releases
are now triggered by pushing a vX.Y.Z tag (version updated manually via
./scripts/bump-version.sh). The pipeline verifies the tag matches the
version in Cargo.toml, builds the 4 platform binaries from the tagged
commit, and publishes the GitHub Release. workflow_dispatch re-publishes
an existing tag.
A panic while the TUI is active used to strand the terminal in raw
mode / alternate screen, making it look like the terminal no longer
accepts input. init_terminal now installs a panic hook that restores
the terminal before delegating to the default hook.

Also add a regression test for opening help over an all-done (empty)
task list and a deterministic random-key fuzz over the event loop.
A code commit without a version bump can never become a release
(releases are tag-driven and the version lives in Cargo.toml), so
require the bump at commit time:

- .githooks/pre-commit blocks code commits (src/, tests/, Cargo
  manifests) that do not bump the version, reuse an existing tag, or
  leave Cargo.lock out of sync; .githooks/post-commit creates the
  vX.Y.Z tag automatically after a version-bump commit. Enable once
  per clone via scripts/install-hooks.sh.
- The version-bump job in ci.yml is the server-side backstop: pushes
  and PRs that change code without bumping the version fail.
- bump-version.sh now edits Cargo.toml + Cargo.lock in the working
  tree only (no commit/tag) to fit the hook-driven flow.
fs::write straight over basilk_data.json could leave a truncated file
behind when interrupted, crashing the next startup on unparseable JSON.
Write to a sibling basilk_data.json.tmp first and rename it over the
target (same directory, so the rename stays on one filesystem), cleaning
up the temp file on failure.
The legacy migration deleted the old versioned file right after writing
the new one, before the recursive Json::check() verified it. Reorder to
write -> verify -> remove, propagate write failures through the new
write_internal Result so a failed migration keeps the legacy file, and
treat the removal itself as best-effort cleanup.
read_notes swallowed every error and returned an empty list, and the
write paths used it (and a panicking read) for their read-modify-write,
so a corrupt data file was silently rewritten with an empty counterpart.
Add a strict internal read (missing file -> None, parse failure -> Err),
use it in Json::write/write_notes so they refuse to write, and make
Json::read return Result so main can report a corrupt data file with
its path before the terminal enters raw mode.
Every mutation deep-copied the whole project or note list for
Json::write/write_notes. The write paths now take &[Project]/&[Note]
and serialize through a borrowed DataWrapperRef, so no clone happens at
the call sites or inside the persistence layer. Behavior is unchanged.
The static VERSION string was an implicit invariant maintained by call
order: writes read it, but only check/read assigned it. The version now
travels with the DataWrapper itself — the write paths take it from the
strictly re-read on-disk wrapper, falling back to the latest known
version when no file exists. This removes the MutexGuard threading and
manual drops in the check paths while preserving the behavior of never
downgrading a version written by a newer binary.
handle_modal_nav mapped Tab to previous and BackTab to next, the
inverse of every list view. Swap them so Tab moves down and BackTab
moves up, and update the modal nav test to assert both directions.
The task title is the stable identity of a task (selection restore,
timer binding, add_time_spent all match the first task by title), so a
duplicate title would silently act on the wrong task. Task::create and
Task::rename now return bool and refuse a title already taken in the
same project; the add/rename handlers keep the input view open on a
rejection instead of silently succeeding.
Previously only --version was recognized and basilk --help launched the
full-screen TUI. Cli::parse now maps --help/-h to a new
CliAction::ShowHelp, main prints the USAGE text and exits like
--version does, and tests/cli.rs asserts the end-to-end behavior.
The details modal computed the estimate percentage inline with f64 and
two decimal places while the timer modal uses Task::estimate_progress
(rounded integer, saturating arithmetic), so the same number was
presented in two different formats. Both now go through
estimate_progress.
Percentages 1..=24 fell through to the White fallback because the
LightMagenta band started at 25. Extend it to 1..=50 so any started
project is colored, and update the threshold test.
…ities on load

Hand-edited JSON can carry statuses/priorities outside the known sets.
The sort keys used position() and None sorts before Some(0), so unknown
values jumped to the front of the list; they now map past the end of
the known keys and sort last. load_items also normalizes Done tasks to
priority NONE on every load, restoring the invariant the list/board
index math relies on. board_sync now documents that a task with an
unknown status keeps the current lane focus.
Note::create refused an empty title but Note::rename accepted one, and
rename never stamped updated_at the way update_body does. rename now
matches create (empty input is a silent no-op) and updates updated_at.
Replace the cloned vec, nine-field dummy Task fallback, and double
clone with a plain Option map plus unwrap_or_default.
Count via iter().filter().count() instead of cloning the whole task
vec, and drop the stray trailing comma in the done-count format.
The two options are compile-time constants, yet they were built once
at startup and threaded through the event loop, handle_key, render,
and render_modal, then cloned again every frame via to_vec(). Construct
them where they are used: View::delete_confirm_items() feeds both
show_delete_item_modal and the delete key handlers (which only need
the option count for navigation). Removes Ui::load_delete_confirm_items
and the delete_confirm_items parameter everywhere.
The second parameter is an absolute row count (Constraint::Length),
not a percentage, so rename the pair to width_pct/height_rows. Clamp
width_pct to 100: a larger value made 100 - width_pct underflow u16
and panic in debug builds.
Rename load_statues_items to load_statuses_items and use map_or for
the timer's running-elapsed fallback.
- Call change_view as a method instead of App::change_view(self, ...)
- Prefill inputs with Input::default().with_value(...) instead of
  cloning the previous input
- Take &[ListItem] instead of &Vec<ListItem> in handle_key,
  handle_change_status_task, handle_change_priority_task, and
  handle_modal_nav
Task/Project/Note::get_current return immutable references, so they
only need a shared borrow. All call sites compile unchanged (&mut
reborrows as &); explicit &mut app arguments in tests were simplified
to &app. No call site needed to keep a mutable borrow.
render_markdown and get_spaced_title both return owned values, so the
note borrow can end before note_scroll is clamped. Reordering the
statements that way drops the per-frame clone of the whole note body
and title. Markdown is still re-parsed every frame, a known trade-off.
- markdown.rs: raw HTML is dropped entirely, not rendered as plain
  text; tables render as plain source only because the pulldown-cmark
  tables extension is off
- view.rs: the Time Consumed line only requires created_at; without
  completed_at format_duration prints "Task not completed"
- tests/cli.rs: the suite covers --version/--help print-and-exit only,
  not terminal setup
- AGENTS.md: drop the nonexistent src/config.rs entry, rewrite the
  half-finished src/app.rs note (App lives in main.rs), and correct
  the key-handler testing note (handlers do change views via
  self.change_view; view_mode must be preset for use_state)
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.

1 participant