Skip to content

Global use and change proyects#7

Merged
jnfire merged 9 commits into
mainfrom
global-use-and-change-proyects
Apr 24, 2026
Merged

Global use and change proyects#7
jnfire merged 9 commits into
mainfrom
global-use-and-change-proyects

Conversation

@jnfire

@jnfire jnfire commented Apr 24, 2026

Copy link
Copy Markdown
Owner

This pull request updates the documentation and user guides to reflect major architectural and feature improvements in the latest versions of time-balance, especially the transition to a global, multi-project, and high-performance caching system. The documentation now emphasizes centralized SQLite storage, atomic balance updates, improved CLI navigation, and a professional user experience.

Key changes include:

Feature and Architecture Documentation

  • Updated CHANGELOG.md, README.md, README.es.md, and docs/ARCHITECTURE.md to document new features: global installation, multi-project support, SQLite backend, high-performance balance caching, and atomic incremental balance updates. [1] [2] [3] [4] [5] [6] [7] [8]

  • Expanded architecture documentation to describe the new database schema, project and record management, atomic update strategies, and the redesigned test suite. [1] [2]

CLI and User Experience

  • Revised CLI guide (docs/CLI-GUIDE.md) to explain the new global, multi-project workflow, standard navigation keys, paginated history, and enhanced configuration and project management menus. [1] [2]

Data Management and Location

  • All documentation now clearly states that data is stored in a centralized SQLite database, with platform-specific default paths listed for user clarity. [1] [2] [3] [4]

Test and Reliability Improvements

  • Documented the new approach to atomic balance updates, cache auditing, and the adaptation of the test suite to the SQLite backend for robust reliability and performance. [1] [2]

Internationalization and Professionalism

  • Improved language in both English and Spanish guides to reflect the professional, global, and multi-project nature of the tool, and clarified installation and usage instructions. [1] [2] [3] [4]

These changes ensure that all user-facing and developer documentation accurately represents the current capabilities and design of time-balance.

Copilot AI review requested due to automatic review settings April 24, 2026 09:08

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request transitions the application to a global SQLite-backed architecture, introducing multi-project support and an incremental balance caching system. The user interface has been modernized using the rich library, and data storage now adheres to XDG standards. Key feedback includes resolving a NameError for the missing migrate_from_json function, addressing SQLite connection leaks in the storage and CLI modules, and optimizing the database schema by including the total_balance column in the initial table creation. Improvements to the KeyboardInterrupt handling are also recommended to ensure a smoother exit flow for users.

Comment thread time_balance/cli.py
Comment thread time_balance/storage.py Outdated
Comment thread time_balance/cli.py Outdated
Comment thread time_balance/storage.py
Comment thread time_balance/cli.py Outdated

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 modernizes time-balance into a global, multi-project CLI backed by centralized SQLite storage, adds an incremental cached-balance model for performance, and refreshes user/developer documentation and tests to match the new architecture.

Changes:

  • Replaced legacy JSON persistence with a SQLite-based DatabaseManager plus cached total_balance updates.
  • Redesigned the CLI UX using rich (dashboard, paginated history, config/project submenus) and adjusted import/export flows.
  • Updated packaging/versioning (dynamic version file, rich dependency), documentation, and test suite (including new balance-cache tests).

Reviewed changes

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

Show a summary per file
File Description
time_balance/storage.py Introduces SQLite DatabaseManager, cached balance logic, and legacy shims
time_balance/io.py Refactors import/export utilities to validate/read/write legacy JSON files
time_balance/i18n.py Updates and expands translations for new menus and navigation
time_balance/constants.py Adds XDG-style global data dir resolution and DB path; version from file
time_balance/cli.py New rich-based UI, pagination, config/project menus, new CLI args
time_balance/init.py Updates public re-exports to reflect new modules/functions
time_balance/VERSION Adds a single-source version file (0.4.1)
tests/test_storage.py Reworks tests around SQLite-based storage manager
tests/test_io.py Updates tests for new export_history / read_history_file behavior
tests/test_cli.py Updates CLI tests to patch DB and capture rich output
tests/test_balance_cache.py Adds focused tests for incremental cache correctness
setup.py Reads version from file; adds rich dependency and package data
pyproject.toml Uses dynamic version from file; declares dependencies and package data
docs/es/DEVELOPMENT.es.md Updates Spanish dev guide for new architecture
docs/es/CLI-GUIDE.es.md Updates Spanish CLI guide for global/multi-project workflow
docs/es/ARCHITECTURE.es.md Updates Spanish architecture doc for SQLite/caching design
docs/DEVELOPMENT.md Updates dev guide for new layering and SQLite persistence
docs/CLI-GUIDE.md Updates CLI guide for new navigation, global usage, and commands
docs/ARCHITECTURE.md Updates architecture doc for SQLite schema and caching strategy
README.md Updates English README for global/multi-project SQLite-based tool
README.es.md Updates Spanish README for global/multi-project SQLite-based tool
CHANGELOG.md Documents new releases/features (0.3.0 / 0.4.1)

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

Comment thread time_balance/cli.py
# Display current configuration
console.print(f"\n [dim]{translate('project_label', lang=lang)}:[/dim] [bold]{project['name']}[/bold]")
console.print(f" [dim]{translate('base_day_label', lang=lang)}:[/dim] [bold]{project['base_hours']}h {project['base_minutes']}m[/bold]")
console.print(f" [dim]Language:[/dim] [bold]{db.get_setting('language', 'auto')}[/bold]")

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

The config menu prints Language: as a hard-coded English string, bypassing the i18n layer used elsewhere. Add a translation key (e.g., language_label) and use translate(...) so the menu stays fully localized.

Suggested change
console.print(f" [dim]Language:[/dim] [bold]{db.get_setting('language', 'auto')}[/bold]")
console.print(f" [dim]{translate('language_label', lang=lang)}:[/dim] [bold]{db.get_setting('language', 'auto')}[/bold]")

Copilot uses AI. Check for mistakes.
Comment thread CHANGELOG.md
- **Global Installation**: The app now stores data in standard system paths (XDG compliant), making it truly global.
- **SQLite Backend**: Replaced JSON storage with SQLite for improved performance and data integrity.
- **Multi-project Support**: New project management menu allows creating, switching, and editing multiple work contexts.
- **Migration Tool**: New `--migrate <file.json>` command to import legacy history files into the new system.

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

Changelog claims a working --migrate <file.json> command exists, but cli.py currently references an undefined migrate_from_json function, so the flag will fail at runtime. Either implement the migration command or adjust the changelog entry until it’s actually available.

Suggested change
- **Migration Tool**: New `--migrate <file.json>` command to import legacy history files into the new system.
- **Migration Preparation**: Added groundwork for importing legacy JSON history files into the new system.

Copilot uses AI. Check for mistakes.
Comment thread time_balance/cli.py
Comment on lines +396 to +398
if args.migrate:
migrate_from_json(args.migrate, lang=lang)
return

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

--migrate will currently crash with NameError because migrate_from_json is referenced but not implemented/imported anywhere. Either implement migrate_from_json in this module (and add tests), or remove the CLI flag/call until the migration flow is ready.

Copilot uses AI. Check for mistakes.
Comment thread time_balance/cli.py
Comment on lines +251 to +254
if mode == constants.MODE_OVERWRITE:
with db._get_connection() as conn:
conn.execute("DELETE FROM records WHERE project_id = ?", (active_id,))

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

This flow reaches into the DB manager’s private _get_connection() to delete records, which makes the CLI depend on internal implementation details. Expose a public method on DatabaseManager for “clear project records” / “delete all records for project” and call that instead.

Copilot uses AI. Check for mistakes.
Comment thread docs/DEVELOPMENT.md
Comment thread time_balance/storage.py
Comment on lines +145 to +157
with self._get_connection() as connection:
cursor = connection.cursor()

# 1. Get old difference if record exists to adjust balance
cursor.execute("SELECT difference FROM records WHERE project_id = ? AND date = ?", (project_id, record_date))
row = cursor.fetchone()
old_difference = row[0] if row else 0

# 2. Upsert the record
cursor.execute("""
INSERT OR REPLACE INTO records (project_id, date, hours, minutes, difference)
VALUES (?, ?, ?, ?, ?)
""", (project_id, record_date, hours, minutes, difference))

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

upsert_record reads old_difference and then writes using a separate statement, but the initial SELECT can occur outside a transaction, so another writer could change the record between the read and the write (breaking the “atomic balance update” guarantee). Start the transaction before reading (e.g., BEGIN IMMEDIATE) and/or use an INSERT ... ON CONFLICT DO UPDATE so the upsert happens without REPLACE semantics (which also resets id/created_at).

Copilot uses AI. Check for mistakes.
Comment thread time_balance/cli.py
choices.append("p")

console.print(nav_msg)
choice = Prompt.ask("", choices=choices, show_choices=False, console=console).lower()

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

This prompt advertises navigation keys as uppercase (V/N/P) but Prompt.ask(..., choices=...) is configured with lowercase-only choices and defaults to case-sensitive validation in Rich. Either set case_sensitive=False or include uppercase variants in choices so users can type the keys as displayed.

Suggested change
choice = Prompt.ask("", choices=choices, show_choices=False, console=console).lower()
choice = Prompt.ask("", choices=choices, show_choices=False, console=console, case_sensitive=False).lower()

Copilot uses AI. Check for mistakes.
Comment thread docs/es/DEVELOPMENT.es.md
# Guía para Desarrolladores

Esta guía proporciona detalles técnicos para desarrolladores que deseen integrar `time-balance` en sus flujos o contribuir a su desarrollo.
Esta guía proporciona detalles técnicos para desarrolladores que deseen contribuir a la versión 0.3.x de `time-balance`.

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

Esta guía indica que las contribuciones son para la “versión 0.3.x”, pero el proyecto ya está en 0.4.1. Actualiza esa referencia para que coincida con la versión real (y con time_balance/VERSION) y evitar confusión.

Suggested change
Esta guía proporciona detalles técnicos para desarrolladores que deseen contribuir a la versión 0.3.x de `time-balance`.
Esta guía proporciona detalles técnicos para desarrolladores que deseen contribuir a la versión 0.4.1 de `time-balance`.

Copilot uses AI. Check for mistakes.
Comment thread docs/es/CLI-GUIDE.es.md
Comment on lines +16 to +17
# Migrar un archivo JSON de legado a un nuevo proyecto global
time-balance --migrate ./ruta/al/historial.json

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

Esta guía documenta --migrate, pero en el código actual (cli.py) se llama a una función inexistente (migrate_from_json), por lo que el flag fallará. O bien se implementa la migración, o se elimina/etiqueta como “pendiente” hasta que funcione.

Copilot uses AI. Check for mistakes.
Comment thread time_balance/io.py
Comment on lines +136 to +138
# import_history was previously handling backups and saving.
# Now cli.py will handle database interaction.
# We keep a simpler version or remove it if cli.py handles everything.

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

import_history appears to have been removed entirely (only a comment remains), which is another breaking change for anyone using the library API. If this removal is intentional, consider providing a deprecation period or a replacement helper that performs the DB import so external callers don’t lose functionality abruptly.

Copilot uses AI. Check for mistakes.
…oved error handling and connection management

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

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


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

Comment thread time_balance/cli.py
Comment on lines 1 to +4
import os
import argparse
from datetime import date, datetime
from typing import Optional

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

cli.py imports os and Optional but neither is used anymore. This will trip linters and makes the module look like it still has OS-dependent behavior.

Suggested change
import os
import argparse
from datetime import date, datetime
from typing import Optional
import argparse
from datetime import date, datetime

Copilot uses AI. Check for mistakes.
Comment thread time_balance/cli.py
Comment on lines +227 to +230
lang_opt = Prompt.ask(
translate("language_prompt", lang=lang, current=db.get_setting("language", "auto")),
choices=["en", "es", "auto"],
default="auto",

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

The language prompt in config_menu hard-codes default="auto". If the current setting is en/es, pressing Enter will unexpectedly reset the language to auto. The default should typically be the current value from settings so Enter keeps the existing selection.

Suggested change
lang_opt = Prompt.ask(
translate("language_prompt", lang=lang, current=db.get_setting("language", "auto")),
choices=["en", "es", "auto"],
default="auto",
current_language = db.get_setting("language", "auto")
lang_opt = Prompt.ask(
translate("language_prompt", lang=lang, current=current_language),
choices=["en", "es", "auto"],
default=current_language,

Copilot uses AI. Check for mistakes.
Comment thread CHANGELOG.md
Comment on lines +5 to +9
## [0.4.2] - 2026-04-24

### Fixed
- **NameError in Migration**: Fixed missing `migrate_from_json` function reported in code review.
- **SQLite Connection Leaks**: Implemented a robust context manager in `DatabaseManager` to ensure all database connections are properly closed after use.

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

Changelog adds a 0.4.2 section, but the package version is read from time_balance/VERSION (currently 0.4.1), so --version and packaging metadata won’t match the changelog. Either bump time_balance/VERSION to 0.4.2 or adjust the changelog heading to the released version.

Copilot uses AI. Check for mistakes.
Comment thread time_balance/cli.py
Comment on lines +325 to +329
try:
h = int(Prompt.ask(translate("base_hours_prompt", lang=lang, current=constants.BASE_HOURS), default=str(constants.BASE_HOURS), console=console))
m = int(Prompt.ask(translate("base_minutes_prompt", lang=lang, current=constants.BASE_MINUTES), default=str(constants.BASE_MINUTES), console=console))
new_id = db.create_project(name, h, m)
db.set_active_project_id(new_id)

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

Project creation only catches ValueError. If the chosen project name already exists, db.create_project() will raise sqlite3.IntegrityError (UNIQUE constraint) and the CLI will crash. Consider catching sqlite3.IntegrityError here and prompting for a different name.

Copilot uses AI. Check for mistakes.
Comment thread docs/ARCHITECTURE.md
- Support for history merging (`merge`) or total replacement (`overwrite`).
Logic to validate and read external JSON files.
- `read_history_file()`: Validates schemas from previous versions (v0.2.x).
- `export_history()`: Generates a JSON dump of the active project.

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

export_history() no longer “generates a JSON dump of the active project” by itself; it simply writes the provided data dict to disk (the CLI constructs the dump). Updating this description will help contributors understand where the export payload is assembled.

Suggested change
- `export_history()`: Generates a JSON dump of the active project.
- `export_history()`: Writes a provided project-history data dict to a JSON file; the CLI assembles the export payload.

Copilot uses AI. Check for mistakes.
Comment thread time_balance/storage.py
Comment on lines +272 to +274
# --- GLOBAL SINGLETON ---
db = DatabaseManager(constants.DATABASE_PATH)

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

The global singleton db = DatabaseManager(constants.DATABASE_PATH) is created at import time, which will create directories and the real user database just by importing time_balance (including during unit tests). This has undesirable side effects and makes it hard to run tests without touching the user environment. Consider lazy-initializing the singleton (e.g., get_db()), or constructing it inside the CLI entrypoints and passing it through (with tests injecting a temp DB).

Copilot uses AI. Check for mistakes.
Comment thread time_balance/storage.py
Comment on lines +279 to +284
project = db.get_project_by_id(project_id)
records_list = db.get_records(project_id)

# Convert list of records to dict by date
records_dict = {r['date']: {'hours': r['hours'], 'minutes': r['minutes'], 'difference': r['difference']} for r in records_list}

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

load_data() assumes the active project exists (project = db.get_project_by_id(project_id) then later indexes project[...]). If active_project_id is stale/corrupted, project can be None and this will crash. Consider handling None with a fallback/reseed or a clearer error.

Suggested change
project = db.get_project_by_id(project_id)
records_list = db.get_records(project_id)
# Convert list of records to dict by date
records_dict = {r['date']: {'hours': r['hours'], 'minutes': r['minutes'], 'difference': r['difference']} for r in records_list}
if project_id is None:
raise RuntimeError("No active project is configured.")
project = db.get_project_by_id(project_id)
if project is None:
raise RuntimeError(f"Active project {project_id} does not exist.")
records_list = db.get_records(project_id)
# Convert list of records to dict by date
records_dict = {r['date']: {'hours': r['hours'], 'minutes': r['minutes'], 'difference': r['difference']} for r in records_list}

Copilot uses AI. Check for mistakes.
Comment thread time_balance/storage.py
Comment on lines +296 to 300
"""Shim for compatibility. Saves metadata to the active project."""
if file_path:
# If a file_path is provided, we might be exporting or using a non-standard flow
# For now, we ignore it and warn, or implement as needed.
pass

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

save_data() shim silently ignores file_path (it just passes). Callers providing a file path may assume it’s respected, but it currently does nothing. Consider raising a clear NotImplementedError/warning or implementing the intended export/custom-path behavior to avoid silent misbehavior.

Copilot uses AI. Check for mistakes.
Comment thread time_balance/constants.py
base_dir = pathlib.Path.home() / "AppData" / "Roaming"
else:
base_dir = pathlib.Path(base_dir)
elif os.uname().sysname == 'Darwin':

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

get_data_dir() uses os.uname() to detect macOS. os.uname is not available on all Python platforms/environments, so this can raise AttributeError even when os.name != 'nt'. Consider using sys.platform == 'darwin' (or platform.system()) to avoid relying on os.uname.

Suggested change
elif os.uname().sysname == 'Darwin':
elif os.sys.platform == 'darwin':

Copilot uses AI. Check for mistakes.
Comment thread time_balance/storage.py
query = "SELECT * FROM records WHERE project_id = ? ORDER BY date DESC"
parameters = [project_id]

if limit:

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

get_records() checks if limit: so limit=0 behaves like limit=None and returns all rows. If callers pass 0 expecting an empty result set, this is surprising. Prefer if limit is not None: (allowing LIMIT 0) or validate limit > 0.

Suggested change
if limit:
if limit is not None:

Copilot uses AI. Check for mistakes.
@jnfire jnfire merged commit edcf643 into main Apr 24, 2026
5 checks passed
@jnfire jnfire deleted the global-use-and-change-proyects branch April 25, 2026 11:35
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.

2 participants