Skip to content

feat: show a read-only notice when the host marks a project read-only - #413

Open
NKeleher wants to merge 1 commit into
mainfrom
feat/read-only-project-banner
Open

NKeleher wants to merge 1 commit into
mainfrom
feat/read-only-project-banner

Conversation

@NKeleher

@NKeleher NKeleher commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Pull Request Summary 🚀

What does this PR do? 📝

If a project's settings folder contains a .readonly marker (cache/{project_id}/settings/.readonly), every page of that project shows the marker's text as a warning banner (:material/lock:). DataSure only reads the marker: it never creates it, never deletes it, and doesn't enforce read-only itself.

Why is this change needed? 🤔

DataSure Cloud hosts the unmodified datasure package from PyPI for IPA's multi-user pilot: one Linux container per signed-in user, with project state synced to Azure Blob Storage and a per-project write lock so only one person edits a project at a time. It's one of three small, independent PRs that let the hosting layer stop working around DataSure (the others: active-project signal #412, headless keyring guidance #414). When another user holds a project's write lock, or a user only has view access, DataSure Cloud opens the project read-only by making its files read-only on disk. DataSure doesn't know this, so the user's first save fails with a raw PermissionError. A marker file is the smallest contract that lets the host say why the project is read-only (for example "Being edited by someone
since 10:42 UTC") before the user loses work. Desktop users are unaffected: without the marker nothing changes.

How was this implemented? 🛠️

  • utils/cache_utils.py: READ_ONLY_MARKER = ".readonly" and read_only_reason(project_id) -> str | None:
    • returns the marker's trimmed text, cut to 300 characters;
    • an empty marker gives a generic message;
    • a missing marker, a directory, or an unreadable or undecodable file means "not read-only", so a stray file can't block the app.
  • app.py: before nav_menu.run(), shows st.warning(reason) when the active project has a reason. Placing it there puts it above every page.
  • Docs: docs/ARCHITECTURE.md (marker contract, under "Cache and data locations"), CHANGELOG.md under [Unreleased].
  • Out of scope, possible follow-up: disabling write actions while the marker exists.

How to test or reproduce ? 🧪

  • uv run python -m pytest tests/utils/test_cache_utils.py tests/test_app.py. New TestReadOnlyReason covers missing, text, empty, long and directory markers. New TestReadOnlyNotice checks the banner is shown with the reason and isn't shown when the project is writable.
  • Manual: open a project, then create <cache>/<project_id>/settings/.readonly containing Being edited by Ana and rerun the page. The banner appears on every page. Delete the file and the banner goes away.
  • Full suite: just test (4585 passed, 6 skipped); just pre-commit-run clean.

Screenshots (if applicable) 📷

N/A. The banner is a standard st.warning.

Checklist ✅

  • I have run and tested my changes locally
  • I have limit this PR to less than 1000 lines of code change (if not, explain why)
  • I have updated/added tests to cover my changes (if applicable)
  • I have updated/added requirements to cover my changes (if applicable): N/A, no new dependencies
  • I have run linting and formatting on any code changes (if applicable)
  • I have updated the documentation (README, etc.) accordingly
  • I have reviewed and resolved any merge conflict

Reviewer Emoji Legend

:code: Meaning
😃👍💯 :smiley: :+1: :100: I like this...

...and I want the author to know it! This is a way to highlight positive parts of a code review.
⭐⭐⭐ :star: :star: :star: Important to fix before PR can be approved...

And I am providing reasons why it needs to be addressed as well as suggested improvements.
⭐⭐ :star: :star: Important to fix but non-blocking for PR approval...

And I am providing suggestions where it could be improved either in this PR or later.
⭐ :star: Give this some thought but non-blocking for PR approval...

...and consider this a suggestion, not a requirement.
❓ :question: I have a question.

This should be a fully formed question with sufficient information and context that requires a response.
📝 :memo: This is an explanatory note, fun fact, or relevant commentary that does not require any action.
⛏ :pick: This is a nitpick.

This does not require any changes and is often better left unsaid. This may include stylistic, formatting, or organization suggestions and should likely be prevented/enforced by linting if they really matter
♻️ :recycle: Suggestion for refactoring.

Should include enough context to be actionable and not be considered a nitpick.

If cache/{project_id}/settings/.readonly exists, app.py shows its text as a
warning above every page. DataSure only reads the marker; hosts that open a
project read-only (e.g. another user holds its write lock) create it, so
users see why edits won't be saved before they hit a write error.
@sonarqubecloud

Copy link
Copy Markdown

@claude claude 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 found no issues

No high-confidence issues detected in this change.

This branch has not been deployed

No deployments
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