fix(ux+sync): side-bar expand respects clicked path + remote→local branch sync (v0.7.40)
All checks were successful
boundary-lint / PR boundary-claim (Lint (push) Has been skipped
boundary-lint / ban-list lint (Lint (push) Successful in 18s
boundary-lint / duplication-deadline (Layer 1/2) (push) Successful in 19s
ci / mutation test (broker) (push) Has been skipped
ci / test-health gate (push) Successful in 17s
Release Publish (Gitea session_helper) / verify-release-tag (push) Successful in 18s
ci / rust release (push) Successful in 2m54s
ci / rust debug (push) Successful in 2m58s
Release Publish (Gitea session_helper) / publish-linux-x86_64 (push) Successful in 3m38s
ci / python (push) Successful in 1m32s

Two user-reported regressions, fixed together because both surfaced
during the same interactive session.

1. Side Bar "Expand this folder" fell through to the input panel
--------------------------------------------------------------

Right-clicking a folder in the side bar surfaced the
``sessions_expand_deferred_directory`` command but Sublime did not
populate the ``paths`` kwarg, so the command landed on the no-args
branch and opened the "Expand remote directory:" input panel — the
exact opposite of "expand the folder I just clicked".

Root cause: ``Side Bar.sublime-menu`` declared the entries without
the ``"args": {"paths": []}`` placeholder. Without that, Sublime
does not auto-fill the right-clicked paths into the command's args
dict (the command class's ``is_visible(paths=...)`` signature alone
is not sufficient on the side-bar context menu).

Fix: add ``"args": {"paths": []}`` to both Sessions side-bar
entries (Expand + Delete Remote File). Pinned by a new
``test_side_bar_menu_declares_paths_placeholder`` regression so a
future menu refactor cannot drop it silently.

2. Remote→local branch sync was a no-op
---------------------------------------

Local→remote checkout already worked (post-checkout hook → marker →
``apply_pending_checkout`` → remote ``git checkout``). But running
``git checkout other-branch`` directly on the remote left the local
cache showing the previous branch's bytes: ``materialise_working_tree``
runs ``git status --porcelain=v2`` on the remote, sees every file as
clean (working tree matches index after the checkout), marks every
file ``skip-worktree`` locally, and fetches **zero** files. The local
cache stubs from the previous branch are now hidden from git but
Sublime keeps opening their stale content.

Fix: in ``_run_track_g_refresh``, capture the local ``.git/HEAD``
commit SHA *before* the tar replacement and again *after*. When they
differ (= remote-side checkout happened), ask the remote
``git diff --name-only -z <old> <new>`` for the exact tracked-file
delta and pass it to ``materialise_working_tree`` via the new
``extra_force_refresh`` kwarg, which fetches and overwrites those
local cache copies. Files unchanged between the two commits stay on
the cheap skip-worktree path.

New helpers:
* ``_read_local_head_commit_sha(local_root)`` — resolves
  ``.git/HEAD`` through both loose refs and ``packed-refs``;
  returns '' when HEAD is unreadable so the caller can short-circuit
  instead of guessing.
* ``_diff_changed_paths_on_remote(host, root, old, new)`` — wraps
  the remote ``git diff --name-only -z`` exec/once call. Returns
  ``()`` on identical SHAs, transport errors, or non-zero git
  exits (rebase garbage-collected the old commit), so a refresh
  with a stale baseline degrades to the previous behaviour rather
  than spamming spurious refresh requests.

New trace event ``git.remote_head_changed`` carries
``prev_head``/``new_head``/``refresh_count`` so post-mortems can
distinguish a branch swap from a no-op refresh.

Tests:
* 5 ``_read_local_head_commit_sha`` cases (loose ref, packed-refs
  fallback, detached HEAD, missing HEAD, unknown ref)
* 4 ``_diff_changed_paths_on_remote`` cases (happy path with argv
  assertion, identical-SHA short-circuit, non-zero exit returns (),
  transport error returns ())
* 2 ``materialise_working_tree(extra_force_refresh=...)`` cases
  (forces fetch even on clean tracked files; deduplicates against
  ``dirty_modified``)

1,367 tests pass; coverage 80.71% (gate=80%).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 22:56:39 +09:00
parent b44f708892
commit 0dc93212de
10 changed files with 406 additions and 13 deletions

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "sessions-sublime" name = "sessions-sublime"
version = "0.7.39" version = "0.7.40"
description = "Sublime-facing Python code for Sessions." description = "Sublime-facing Python code for Sessions."
requires-python = ">=3.8" requires-python = ">=3.8"
license = {text = "MIT"} license = {text = "MIT"}

12
rust/Cargo.lock generated
View File

@@ -221,7 +221,7 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]] [[package]]
name = "local_bridge" name = "local_bridge"
version = "0.7.39" version = "0.7.40"
dependencies = [ dependencies = [
"base64", "base64",
"glob", "glob",
@@ -432,7 +432,7 @@ dependencies = [
[[package]] [[package]]
name = "session_helper" name = "session_helper"
version = "0.7.39" version = "0.7.40"
dependencies = [ dependencies = [
"base64", "base64",
"notify", "notify",
@@ -443,7 +443,7 @@ dependencies = [
[[package]] [[package]]
name = "session_protocol" name = "session_protocol"
version = "0.7.39" version = "0.7.40"
dependencies = [ dependencies = [
"base64", "base64",
"serde", "serde",
@@ -452,14 +452,14 @@ dependencies = [
[[package]] [[package]]
name = "sessions_askpass" name = "sessions_askpass"
version = "0.7.39" version = "0.7.40"
dependencies = [ dependencies = [
"tempfile", "tempfile",
] ]
[[package]] [[package]]
name = "sessions_native" name = "sessions_native"
version = "0.7.39" version = "0.7.40"
dependencies = [ dependencies = [
"base64", "base64",
"notify", "notify",
@@ -773,7 +773,7 @@ dependencies = [
[[package]] [[package]]
name = "workspace_identity" name = "workspace_identity"
version = "0.7.39" version = "0.7.40"
[[package]] [[package]]
name = "zmij" name = "zmij"

View File

@@ -12,7 +12,7 @@ resolver = "2"
[workspace.package] [workspace.package]
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
version = "0.7.39" version = "0.7.40"
authors = ["Myeongseon Choi <key262yek@gmail.com>"] authors = ["Myeongseon Choi <key262yek@gmail.com>"]
repository = "https://git.teahaven.kr/sublime-rs/sessions" repository = "https://git.teahaven.kr/sublime-rs/sessions"
homepage = "https://git.teahaven.kr/sublime-rs/sessions" homepage = "https://git.teahaven.kr/sublime-rs/sessions"

View File

@@ -1,10 +1,12 @@
[ [
{ {
"caption": "Sessions: Expand this folder", "caption": "Sessions: Expand this folder",
"command": "sessions_expand_deferred_directory" "command": "sessions_expand_deferred_directory",
"args": {"paths": []}
}, },
{ {
"caption": "Sessions: Delete Remote File", "caption": "Sessions: Delete Remote File",
"command": "sessions_delete_remote_file" "command": "sessions_delete_remote_file",
"args": {"paths": []}
} }
] ]

View File

@@ -7078,6 +7078,89 @@ def _read_local_head_branch(local_root: Path) -> str:
return "" return ""
def _read_local_head_commit_sha(local_root: Path) -> str:
"""Return the 40-char commit SHA HEAD resolves to, or ''.
Used by the remote→local branch-sync path to detect that a remote
``git checkout`` happened between two refreshes (HEAD points to a
different commit after ``fetch_remote_dot_git`` rewrote ``.git``).
Resolves both loose refs (``.git/refs/heads/<branch>``) and
packed-refs entries; returns '' for unreadable HEADs so callers
can short-circuit instead of touching the working tree blindly.
"""
git_dir = local_root / ".git"
head_path = git_dir / "HEAD"
try:
text = head_path.read_text(encoding="utf-8").strip()
except OSError:
return ""
if text.startswith("ref: "):
ref = text[len("ref: ") :].strip()
ref_path = git_dir / ref
try:
return ref_path.read_text(encoding="utf-8").strip()
except OSError:
pass
# Packed-refs fallback for branches whose loose ref was packed
# by a recent ``git gc`` on the remote.
try:
packed = (git_dir / "packed-refs").read_text(encoding="utf-8")
except OSError:
return ""
for line in packed.splitlines():
stripped = line.strip()
if not stripped or stripped.startswith(("#", "^")):
continue
parts = stripped.split(maxsplit=1)
if len(parts) == 2 and parts[1] == ref:
return parts[0]
return ""
# Detached HEAD — text is already a commit SHA.
if len(text) == 40 and all(c in "0123456789abcdef" for c in text.lower()):
return text
return ""
def _diff_changed_paths_on_remote(
host_alias: str,
remote_root: str,
old_sha: str,
new_sha: str,
) -> Tuple[str, ...]:
"""Ask the remote which tracked files differ between two commits.
Returns the empty tuple when either SHA is missing or the diff
fails (e.g. rebase garbage-collected ``old_sha``); the caller
treats that as "no extra refresh known", letting the existing
skip-worktree path keep stale local bytes — better than corrupting
the local cache by guessing.
"""
if not old_sha or not new_sha or old_sha == new_sha:
return ()
try:
result = execute_remote_exec_once(
host_alias,
argv=(
"git",
"-C",
remote_root,
"diff",
"--name-only",
"-z",
old_sha,
new_sha,
),
cwd=remote_root,
timeout_ms=30_000,
)
except SessionHelperStartError:
return ()
if result.exit_code != 0 or result.timed_out:
return ()
payload = (result.stdout or "").rstrip("\x00")
return tuple(entry for entry in payload.split("\x00") if entry)
def _synthesize_pending_checkout_if_local_head_diverged(repo) -> None: # noqa: ANN001 def _synthesize_pending_checkout_if_local_head_diverged(repo) -> None: # noqa: ANN001
"""Write a synthetic marker when local HEAD branch differs from baseline. """Write a synthetic marker when local HEAD branch differs from baseline.
@@ -7251,6 +7334,20 @@ def _run_track_g_refresh(
ok_repos += 1 ok_repos += 1
continue continue
# Capture the local HEAD commit BEFORE the tar replacement so
# that, if it changes after the fetch (= a remote ``git
# checkout`` happened between refreshes), we can ask the
# remote which tracked files differ between the two commits
# and refresh just those local cache copies. Without this,
# the materialise pass would mark every clean tracked file
# as ``skip-worktree`` and Sublime keeps showing the previous
# branch's bytes.
prev_local_head_sha = (
_read_local_head_commit_sha(repo.local_root)
if local_dot_git_present
else ""
)
fetch_result = fetch_remote_dot_git(host_alias, repo) fetch_result = fetch_remote_dot_git(host_alias, repo)
_trace_event( _trace_event(
"git.dot_git_fetch", "git.dot_git_fetch",
@@ -7287,7 +7384,33 @@ def _run_track_g_refresh(
remote_root=repo.remote_root, remote_root=repo.remote_root,
error=str(error), error=str(error),
) )
materialise_result = materialise_working_tree(host_alias, repo) new_local_head_sha = _read_local_head_commit_sha(repo.local_root)
extra_refresh: Tuple[str, ...] = ()
if (
prev_local_head_sha
and new_local_head_sha
and prev_local_head_sha != new_local_head_sha
):
extra_refresh = _diff_changed_paths_on_remote(
host_alias,
repo.remote_root,
prev_local_head_sha,
new_local_head_sha,
)
_trace_event(
"git.remote_head_changed",
host_alias=host_alias,
remote_root=repo.remote_root,
prev_head=prev_local_head_sha,
new_head=new_local_head_sha,
refresh_count=len(extra_refresh),
)
materialise_result = materialise_working_tree(
host_alias,
repo,
extra_force_refresh=extra_refresh,
)
_trace_event( _trace_event(
"git.materialise", "git.materialise",
host_alias=host_alias, host_alias=host_alias,

View File

@@ -220,6 +220,7 @@ def materialise_working_tree(
exec_once: Optional[ExecOnceFn] = None, exec_once: Optional[ExecOnceFn] = None,
read_file: Optional[ReadFileFn] = None, read_file: Optional[ReadFileFn] = None,
git_local: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run, git_local: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
extra_force_refresh: Iterable[str] = (),
) -> MaterialiseResult: ) -> MaterialiseResult:
"""Apply the v0 materialisation policy against one repo. """Apply the v0 materialisation policy against one repo.
@@ -323,8 +324,17 @@ def materialise_working_tree(
# 3. fetch dirty file content. Sequential reads in v0 — these are # 3. fetch dirty file content. Sequential reads in v0 — these are
# bounded by the user's actually-edited file count, not repo # bounded by the user's actually-edited file count, not repo
# size, so the round-trip cost is acceptable. # size, so the round-trip cost is acceptable.
#
# ``extra_force_refresh`` carries paths the caller already knows are
# stale even though remote ``git status`` calls them clean — e.g.
# files that changed between commits across a remote-side branch
# checkout. Without this hatch the local cache keeps the previous
# branch's bytes (skip-worktree hides the staleness from git but
# Sublime opens the wrong content).
refresh_set = set(classification.dirty_modified)
refresh_set.update(extra_force_refresh)
fetched = 0 fetched = 0
for relative in classification.dirty_modified: for relative in sorted(refresh_set):
remote_path = "{}/{}".format(repo.remote_root.rstrip("/"), relative) remote_path = "{}/{}".format(repo.remote_root.rstrip("/"), relative)
local_path = repo.local_root / relative local_path = repo.local_root / relative
try: try:

View File

@@ -50,3 +50,33 @@ def test_command_palette_prioritizes_recent_workspace_entry() -> None:
# ``sessions_show_dev_commands`` is false (the default). # ``sessions_show_dev_commands`` is false (the default).
assert "sessions_open_remote_marimo" in palette_command_set assert "sessions_open_remote_marimo" in palette_command_set
assert "sessions_stop_remote_marimo" in palette_command_set assert "sessions_stop_remote_marimo" in palette_command_set
def test_side_bar_menu_declares_paths_placeholder() -> None:
"""Side Bar context-menu commands must carry ``"args": {"paths": []}``.
Without that placeholder Sublime does NOT auto-populate ``paths`` from
the right-clicked items, which makes ``sessions_expand_deferred_directory``
and ``sessions_delete_remote_file`` fall through to the no-arg path
(input panel for expand, status warning for delete) instead of acting
on the clicked folder/file. Pinned to catch a regression where the
placeholder gets dropped during a menu refactor.
"""
menu_path = Path(__file__).resolve().parents[1] / "Side Bar.sublime-menu"
payload = json.loads(menu_path.read_text(encoding="utf-8"))
sessions_entries = [
item for item in payload if str(item.get("command", "")).startswith("sessions_")
]
assert sessions_entries, (
"expected at least one Sessions entry in Side Bar.sublime-menu"
)
for item in sessions_entries:
args = item.get("args")
assert isinstance(args, dict), (
"Side-bar entry {!r} must declare an 'args' dict so Sublime can "
"inject the clicked paths.".format(item.get("command"))
)
assert args.get("paths") == [], (
"Side-bar entry {!r} must declare 'paths': [] as the placeholder "
"Sublime fills with the right-clicked paths.".format(item.get("command"))
)

View File

@@ -144,3 +144,134 @@ def test_synthesize_no_op_for_detached_head(tmp_path: Path, monkeypatch) -> None
commands._synthesize_pending_checkout_if_local_head_diverged(repo) commands._synthesize_pending_checkout_if_local_head_diverged(repo)
marker = repo.local_root / ".git" / "SESSIONS_PENDING_CHECKOUT" marker = repo.local_root / ".git" / "SESSIONS_PENDING_CHECKOUT"
assert not marker.exists() assert not marker.exists()
# --- _read_local_head_commit_sha + _diff_changed_paths_on_remote ---
#
# These power the remote→local branch-sync path: when remote ``git
# checkout`` rewrites ``.git/HEAD`` between two refreshes, the
# materialise pass needs to know which tracked files changed so it can
# overwrite local cache copies (otherwise skip-worktree hides the
# stale bytes).
def test_read_local_head_commit_sha_resolves_loose_branch_ref(
tmp_path: Path,
) -> None:
repo = _make_repo(tmp_path)
git_dir = repo.local_root / ".git"
(git_dir / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8")
refs_main = git_dir / "refs" / "heads" / "main"
refs_main.parent.mkdir(parents=True, exist_ok=True)
refs_main.write_text("abcdef1234567890abcdef1234567890abcdef12\n", encoding="utf-8")
assert commands._read_local_head_commit_sha(repo.local_root) == (
"abcdef1234567890abcdef1234567890abcdef12"
)
def test_read_local_head_commit_sha_falls_back_to_packed_refs(
tmp_path: Path,
) -> None:
repo = _make_repo(tmp_path)
git_dir = repo.local_root / ".git"
(git_dir / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8")
(git_dir / "packed-refs").write_text(
"# pack-refs with: peeled fully-peeled sorted\n"
"abcdef1234567890abcdef1234567890abcdef12 refs/heads/main\n"
"^cafe1234cafe1234cafe1234cafe1234cafe1234\n",
encoding="utf-8",
)
assert commands._read_local_head_commit_sha(repo.local_root) == (
"abcdef1234567890abcdef1234567890abcdef12"
)
def test_read_local_head_commit_sha_returns_detached_head(tmp_path: Path) -> None:
repo = _make_repo(tmp_path)
sha = "deadbeef00000000000000000000000000000000"
(repo.local_root / ".git" / "HEAD").write_text(sha + "\n", encoding="utf-8")
assert commands._read_local_head_commit_sha(repo.local_root) == sha
def test_read_local_head_commit_sha_returns_empty_when_unreadable(
tmp_path: Path,
) -> None:
repo = _make_repo(tmp_path)
# No HEAD written.
assert commands._read_local_head_commit_sha(repo.local_root) == ""
def test_read_local_head_commit_sha_returns_empty_for_unknown_ref(
tmp_path: Path,
) -> None:
repo = _make_repo(tmp_path)
(repo.local_root / ".git" / "HEAD").write_text(
"ref: refs/heads/missing\n", encoding="utf-8"
)
assert commands._read_local_head_commit_sha(repo.local_root) == ""
def test_diff_changed_paths_on_remote_returns_files(monkeypatch) -> None:
from sessions.ssh_file_transport import RemoteExecOnceResult
captured: list = []
def fake_exec(host_alias, *, argv, cwd, timeout_ms):
captured.append((host_alias, tuple(argv), cwd))
return RemoteExecOnceResult(
exit_code=0,
stdout="src/main.py\x00pkg/a.py\x00",
stderr="",
timed_out=False,
)
monkeypatch.setattr(commands, "execute_remote_exec_once", fake_exec)
out = commands._diff_changed_paths_on_remote(
"prod", "/srv/ws", "old1234old1234", "new5678new5678"
)
assert out == ("src/main.py", "pkg/a.py")
assert captured[0][1] == (
"git",
"-C",
"/srv/ws",
"diff",
"--name-only",
"-z",
"old1234old1234",
"new5678new5678",
)
def test_diff_changed_paths_on_remote_handles_identical_shas() -> None:
"""If old == new, skip the round-trip entirely."""
out = commands._diff_changed_paths_on_remote("prod", "/srv/ws", "same", "same")
assert out == ()
def test_diff_changed_paths_on_remote_returns_empty_on_failure(monkeypatch) -> None:
"""Diff failures (e.g. rebase garbage-collected old SHA) yield ()."""
from sessions.ssh_file_transport import RemoteExecOnceResult
def fake_exec(host_alias, *, argv, cwd, timeout_ms):
return RemoteExecOnceResult(
exit_code=128,
stdout="",
stderr="fatal: bad revision",
timed_out=False,
)
monkeypatch.setattr(commands, "execute_remote_exec_once", fake_exec)
out = commands._diff_changed_paths_on_remote("prod", "/srv/ws", "old", "new")
assert out == ()
def test_diff_changed_paths_on_remote_handles_transport_error(monkeypatch) -> None:
"""SessionHelperStartError must not bubble out — return () so the
caller still runs the materialise without the extra refresh."""
from sessions.connect_preflight import SessionHelperStartError
def fake_exec(*args, **kwargs):
raise SessionHelperStartError("ssh down")
monkeypatch.setattr(commands, "execute_remote_exec_once", fake_exec)
assert commands._diff_changed_paths_on_remote("prod", "/srv/ws", "old", "new") == ()

View File

@@ -339,3 +339,100 @@ def test_materialise_reports_dirty_fetch_exception(tmp_path: Path) -> None:
# Skip-worktree count reflects the work that completed before the # Skip-worktree count reflects the work that completed before the
# fetch failure (zero clean tracked here, but the field is set). # fetch failure (zero clean tracked here, but the field is set).
assert result.skip_worktree_set == 0 assert result.skip_worktree_set == 0
def test_materialise_extra_force_refresh_pulls_clean_files_too(
tmp_path: Path,
) -> None:
"""Caller-supplied refresh list overrides the clean-tracked default.
Exercises the remote→local branch-sync hatch: when the caller has
already detected a HEAD swap and asked for specific paths to be
refreshed, ``materialise_working_tree`` fetches them via
``read_file`` even though remote ``git status`` reports them
clean. Without this hatch the local cache keeps the previous
branch's bytes.
"""
repo = _make_repo(tmp_path)
repo.local_root.mkdir()
def fake_exec(
host_alias: str, argv, cwd: str, timeout_ms: int
) -> RemoteExecOnceResult:
if "ls-files" in argv:
return _ok_exec(stdout="README.md\x00src/main.py\x00pkg/a.py\x00")
if "status" in argv:
return _ok_exec(stdout="") # everything clean — branch swap case
return _ok_exec(exit_code=2, stderr="unexpected argv")
read_calls: List[str] = []
def fake_read(
host_alias: str, request: RemoteReadFileRequest
) -> RemoteReadFileResult:
read_calls.append(request.remote_absolute_path)
return _ok_read(b"new branch bytes\n")
def fake_git_local(argv, **kwargs: Any) -> subprocess.CompletedProcess[str]:
return SimpleNamespace(returncode=0, stdout="", stderr="") # type: ignore[return-value]
result = materialise_working_tree(
"guanine",
repo,
exec_once=fake_exec,
read_file=fake_read,
git_local=fake_git_local,
extra_force_refresh=("README.md", "pkg/a.py"),
)
assert result.ok
assert result.error_detail is None
# Skip-worktree still set on every clean tracked path; the refresh
# list does not subtract from clean_tracked, it just forces extra fetches.
assert result.skip_worktree_set == 3
# Both forced paths fetched + written. ``src/main.py`` was clean
# AND not in the refresh list — left alone (skip-worktree only).
assert sorted(read_calls) == ["/srv/ws/README.md", "/srv/ws/pkg/a.py"]
assert result.files_fetched == 2
assert (repo.local_root / "README.md").read_bytes() == b"new branch bytes\n"
assert (repo.local_root / "pkg" / "a.py").read_bytes() == b"new branch bytes\n"
def test_materialise_extra_force_refresh_merges_with_dirty_modified(
tmp_path: Path,
) -> None:
"""If a path is both ``dirty_modified`` and force-refreshed, fetch once."""
repo = _make_repo(tmp_path)
repo.local_root.mkdir()
def fake_exec(
host_alias: str, argv, cwd: str, timeout_ms: int
) -> RemoteExecOnceResult:
if "ls-files" in argv:
return _ok_exec(stdout="src/main.py\x00")
if "status" in argv:
return _ok_exec(stdout="1 .M N... 100644 100644 100644 a b src/main.py\x00")
return _ok_exec(exit_code=2)
read_calls: List[str] = []
def fake_read(
host_alias: str, request: RemoteReadFileRequest
) -> RemoteReadFileResult:
read_calls.append(request.remote_absolute_path)
return _ok_read(b"x")
def fake_git_local(argv, **kwargs: Any) -> subprocess.CompletedProcess[str]:
return SimpleNamespace(returncode=0, stdout="", stderr="") # type: ignore[return-value]
result = materialise_working_tree(
"h",
repo,
exec_once=fake_exec,
read_file=fake_read,
git_local=fake_git_local,
extra_force_refresh=("src/main.py",),
)
assert result.ok
assert read_calls == ["/srv/ws/src/main.py"] # exactly once, not duplicated
assert result.files_fetched == 1

2
uv.lock generated
View File

@@ -854,7 +854,7 @@ wheels = [
[[package]] [[package]]
name = "sessions-sublime" name = "sessions-sublime"
version = "0.7.39" version = "0.7.40"
source = { virtual = "." } source = { virtual = "." }
[package.dev-dependencies] [package.dev-dependencies]