remo-cli 2.2.0rc5__tar.gz → 2.2.0rc6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (435) hide show
  1. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/PKG-INFO +1 -1
  2. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/docs/web-session-interface.md +6 -2
  3. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/AppShell.css +7 -0
  4. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/AppShell.tsx +38 -5
  5. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/ShortcutsModal.tsx +2 -1
  6. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/TerminalCard.css +50 -7
  7. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/TerminalCard.tsx +75 -26
  8. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/WorkspacePane.tsx +33 -6
  9. remo_cli-2.2.0rc6/frontend/src/lib/fullscreen.ts +38 -0
  10. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/state/useConsoleKeyboard.ts +26 -2
  11. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/state/workspace.ts +58 -9
  12. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/pyproject.toml +1 -1
  13. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/uv.lock +1 -1
  14. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-analyze/SKILL.md +0 -0
  15. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-checklist/SKILL.md +0 -0
  16. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-clarify/SKILL.md +0 -0
  17. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-constitution/SKILL.md +0 -0
  18. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-converge/SKILL.md +0 -0
  19. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-commit/SKILL.md +0 -0
  20. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-feature/SKILL.md +0 -0
  21. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-initialize/SKILL.md +0 -0
  22. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-remote/SKILL.md +0 -0
  23. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-validate/SKILL.md +0 -0
  24. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-implement/SKILL.md +0 -0
  25. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-plan/SKILL.md +0 -0
  26. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-specify/SKILL.md +0 -0
  27. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-tasks/SKILL.md +0 -0
  28. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.claude/skills/speckit-taskstoissues/SKILL.md +0 -0
  29. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.devcontainer/devcontainer.json +0 -0
  30. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.dockerignore +0 -0
  31. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.github/dependabot.yml +0 -0
  32. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.github/workflows/ci.yml +0 -0
  33. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.github/workflows/provision.yml +0 -0
  34. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.github/workflows/release.yml +0 -0
  35. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.github/workflows/smoke-test-cleanup.yml +0 -0
  36. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.github/workflows/smoke-test.yml +0 -0
  37. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.github/workflows/teardown.yml +0 -0
  38. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.gitignore +0 -0
  39. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
  40. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
  41. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.maverick/checkpoints/feature/validation_complete.json +0 -0
  42. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/.registry +0 -0
  43. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/README.md +0 -0
  44. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.commit.md +0 -0
  45. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.feature.md +0 -0
  46. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.initialize.md +0 -0
  47. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.remote.md +0 -0
  48. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.validate.md +0 -0
  49. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/config-template.yml +0 -0
  50. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/extension.yml +0 -0
  51. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/git-config.yml +0 -0
  52. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/bash/auto-commit.sh +0 -0
  53. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/bash/create-new-feature.sh +0 -0
  54. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/bash/git-common.sh +0 -0
  55. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/bash/initialize-repo.sh +0 -0
  56. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/powershell/auto-commit.ps1 +0 -0
  57. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 +0 -0
  58. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/powershell/git-common.ps1 +0 -0
  59. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 +0 -0
  60. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/extensions.yml +0 -0
  61. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/feature.json +0 -0
  62. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/init-options.json +0 -0
  63. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/integration.json +0 -0
  64. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/integrations/claude.manifest.json +0 -0
  65. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/integrations/speckit.manifest.json +0 -0
  66. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/memory/constitution.md +0 -0
  67. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/scripts/bash/check-prerequisites.sh +0 -0
  68. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/scripts/bash/common.sh +0 -0
  69. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/scripts/bash/create-new-feature.sh +0 -0
  70. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/scripts/bash/setup-plan.sh +0 -0
  71. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/scripts/bash/setup-tasks.sh +0 -0
  72. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/scripts/bash/update-agent-context.sh +0 -0
  73. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/templates/agent-file-template.md +0 -0
  74. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/templates/checklist-template.md +0 -0
  75. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/templates/constitution-template.md +0 -0
  76. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/templates/plan-template.md +0 -0
  77. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/templates/spec-template.md +0 -0
  78. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/templates/tasks-template.md +0 -0
  79. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/workflows/speckit/workflow.yml +0 -0
  80. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/.specify/workflows/workflow-registry.json +0 -0
  81. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/AGENTS.md +0 -0
  82. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/CLAUDE.md +0 -0
  83. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/CONTRIBUTING.md +0 -0
  84. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/Dockerfile +0 -0
  85. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/LICENSE +0 -0
  86. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/README.md +0 -0
  87. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/.ansible-lint +0 -0
  88. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/README.md +0 -0
  89. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/ansible.cfg +0 -0
  90. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/aws_configure.yml +0 -0
  91. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/aws_resize.yml +0 -0
  92. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/aws_site.yml +0 -0
  93. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/aws_teardown.yml +0 -0
  94. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/group_vars/all.yml +0 -0
  95. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/group_vars/incus_containers.yml +0 -0
  96. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/group_vars/proxmox_containers.yml +0 -0
  97. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/hetzner_configure.yml +0 -0
  98. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/hetzner_provision.yml +0 -0
  99. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/hetzner_resize.yml +0 -0
  100. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/hetzner_site.yml +0 -0
  101. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/hetzner_teardown.yml +0 -0
  102. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/incus_bootstrap.yml +0 -0
  103. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/incus_configure.yml +0 -0
  104. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/incus_provision.yml +0 -0
  105. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/incus_resize.yml +0 -0
  106. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/incus_site.yml +0 -0
  107. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/incus_teardown.yml +0 -0
  108. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/inventory/hosts.yml +0 -0
  109. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/inventory/incus_containers.yml +0 -0
  110. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/proxmox_bootstrap.yml +0 -0
  111. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/proxmox_configure.yml +0 -0
  112. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/proxmox_provision.yml +0 -0
  113. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/proxmox_resize.yml +0 -0
  114. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/proxmox_site.yml +0 -0
  115. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/proxmox_teardown.yml +0 -0
  116. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/requirements.yml +0 -0
  117. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/defaults/main.yml +0 -0
  118. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
  119. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
  120. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/main.yml +0 -0
  121. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
  122. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
  123. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
  124. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/deacon/defaults/main.yml +0 -0
  125. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/deacon/tasks/main.yml +0 -0
  126. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/devcontainers/tasks/main.yml +0 -0
  127. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/docker/handlers/main.yml +0 -0
  128. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/docker/tasks/main.yml +0 -0
  129. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/fzf/tasks/main.yml +0 -0
  130. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/github_cli/tasks/main.yml +0 -0
  131. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
  132. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
  133. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
  134. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
  135. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
  136. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/defaults/main.yml +0 -0
  137. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/handlers/main.yml +0 -0
  138. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/meta/main.yml +0 -0
  139. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/tasks/main.yml +0 -0
  140. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
  141. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
  142. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
  143. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
  144. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/nodejs/defaults/main.yml +0 -0
  145. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/nodejs/tasks/main.yml +0 -0
  146. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
  147. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
  148. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
  149. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
  150. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
  151. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/meta/main.yml +0 -0
  152. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
  153. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
  154. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
  155. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
  156. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
  157. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/defaults/main.yml +0 -0
  158. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/tasks/main.yml +0 -0
  159. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
  160. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/templates/project-launch.sh.j2 +0 -0
  161. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
  162. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/templates/remo-host.sh.j2 +0 -0
  163. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/zellij/defaults/main.yml +0 -0
  164. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/zellij/tasks/main.yml +0 -0
  165. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
  166. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
  167. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/ansible/tasks/configure_dev_tools.yml +0 -0
  168. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/docker/Dockerfile +0 -0
  169. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/docker/compose.example.yml +0 -0
  170. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/docker/entrypoint.sh +0 -0
  171. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/docs/aws.md +0 -0
  172. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/docs/hetzner.md +0 -0
  173. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/docs/incus.md +0 -0
  174. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/docs/install.sh +0 -0
  175. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/docs/proxmox.md +0 -0
  176. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/docs/remo-web-speckit-input.md +0 -0
  177. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/docs/remo-web.html +0 -0
  178. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/index.html +0 -0
  179. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/package-lock.json +0 -0
  180. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/package.json +0 -0
  181. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/public/README.md +0 -0
  182. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/public/dist__ghostty-vt.wasm +0 -0
  183. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/public/ghostty-vt.wasm +0 -0
  184. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/scripts/copy-ghostty-wasm.mjs +0 -0
  185. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/api/client.ts +0 -0
  186. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/AppRoot.tsx +0 -0
  187. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/AwaitingAdoption.css +0 -0
  188. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/AwaitingAdoption.tsx +0 -0
  189. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/OfflineOverlay.css +0 -0
  190. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/OfflineOverlay.tsx +0 -0
  191. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/PairToSync.css +0 -0
  192. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/PairToSync.tsx +0 -0
  193. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/SessionRail.css +0 -0
  194. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/SessionRail.tsx +0 -0
  195. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/SettingsPage.css +0 -0
  196. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/SettingsPage.tsx +0 -0
  197. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/ShortcutsModal.css +0 -0
  198. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/TopBar.css +0 -0
  199. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/TopBar.tsx +0 -0
  200. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/WorkspacePane.css +0 -0
  201. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/providerMeta.ts +0 -0
  202. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/components/railModel.ts +0 -0
  203. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/lib/clipboard.ts +0 -0
  204. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/main.tsx +0 -0
  205. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/state/discovery.ts +0 -0
  206. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/state/fonts.ts +0 -0
  207. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/state/health.ts +0 -0
  208. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/state/settings.ts +0 -0
  209. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/terminal/.gitkeep +0 -0
  210. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/terminal/GhosttyRenderer.ts +0 -0
  211. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/terminal/RendererAdapter.ts +0 -0
  212. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/terminal/TerminalConnection.ts +0 -0
  213. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/terminal/XtermRenderer.ts +0 -0
  214. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/terminal/defaultRenderer.ts +0 -0
  215. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/theme/fonts.ts +0 -0
  216. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/src/theme/tokens.css +0 -0
  217. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/tsconfig.json +0 -0
  218. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/frontend/vite.config.ts +0 -0
  219. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/install.sh +0 -0
  220. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/maverick.yaml +0 -0
  221. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/playwright.config.ts +0 -0
  222. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/project-menu.sh +0 -0
  223. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/remo +0 -0
  224. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/run.sh +0 -0
  225. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/scripts/setup-aws-oidc.sh +0 -0
  226. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
  227. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
  228. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
  229. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
  230. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/data-model.md +0 -0
  231. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/plan.md +0 -0
  232. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
  233. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/research.md +0 -0
  234. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/spec.md +0 -0
  235. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/tasks.md +0 -0
  236. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/checklists/requirements.md +0 -0
  237. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
  238. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
  239. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/data-model.md +0 -0
  240. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/plan.md +0 -0
  241. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/quickstart.md +0 -0
  242. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/research.md +0 -0
  243. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/spec.md +0 -0
  244. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/tasks.md +0 -0
  245. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
  246. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/data-model.md +0 -0
  247. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/plan.md +0 -0
  248. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/quickstart.md +0 -0
  249. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/research.md +0 -0
  250. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/spec.md +0 -0
  251. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/tasks.md +0 -0
  252. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/004-proxmox-container-support/spec.md +0 -0
  253. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/checklists/requirements.md +0 -0
  254. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/contracts/cli-surface.md +0 -0
  255. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/data-model.md +0 -0
  256. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/plan.md +0 -0
  257. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/quickstart.md +0 -0
  258. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/research.md +0 -0
  259. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/spec.md +0 -0
  260. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/tasks.md +0 -0
  261. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/checklists/requirements.md +0 -0
  262. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/contracts/remo-host-protocol.md +0 -0
  263. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/contracts/rest-api.md +0 -0
  264. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/contracts/terminal-websocket.md +0 -0
  265. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/data-model.md +0 -0
  266. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/plan.md +0 -0
  267. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/quickstart.md +0 -0
  268. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/research.md +0 -0
  269. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/spec.md +0 -0
  270. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/tasks.md +0 -0
  271. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/011-web-adopt/checklists/requirements.md +0 -0
  272. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/011-web-adopt/contracts/cli-web-adopt.md +0 -0
  273. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/011-web-adopt/contracts/setup-api.md +0 -0
  274. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/011-web-adopt/data-model.md +0 -0
  275. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/011-web-adopt/plan.md +0 -0
  276. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/011-web-adopt/quickstart-results.md +0 -0
  277. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/011-web-adopt/quickstart.md +0 -0
  278. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/011-web-adopt/research.md +0 -0
  279. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/011-web-adopt/spec.md +0 -0
  280. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/011-web-adopt/tasks.md +0 -0
  281. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/contracts/cli-web-adopt.md +0 -0
  282. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/contracts/pairing-api.md +0 -0
  283. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/contracts/setup-api.md +0 -0
  284. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/data-model.md +0 -0
  285. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/plan.md +0 -0
  286. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/quickstart.md +0 -0
  287. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/research.md +0 -0
  288. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/spec.md +0 -0
  289. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/tasks.md +0 -0
  290. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/__init__.py +0 -0
  291. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/__main__.py +0 -0
  292. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/cli/__init__.py +0 -0
  293. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/cli/cp.py +0 -0
  294. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/cli/main.py +0 -0
  295. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/__init__.py +0 -0
  296. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/aws.py +0 -0
  297. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/hetzner.py +0 -0
  298. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/incus.py +0 -0
  299. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/proxmox.py +0 -0
  300. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/cli/shell.py +0 -0
  301. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/cli/web.py +0 -0
  302. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/__init__.py +0 -0
  303. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/ansible_runner.py +0 -0
  304. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/completion.py +0 -0
  305. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/config.py +0 -0
  306. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/known_hosts.py +0 -0
  307. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/output.py +0 -0
  308. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/picker.py +0 -0
  309. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/remo_host_client.py +0 -0
  310. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/rsync.py +0 -0
  311. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/snapshot.py +0 -0
  312. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/ssh.py +0 -0
  313. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/validation.py +0 -0
  314. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/version.py +0 -0
  315. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/core/web_adopt.py +0 -0
  316. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/models/__init__.py +0 -0
  317. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/models/capability.py +0 -0
  318. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/models/discovery.py +0 -0
  319. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/models/host.py +0 -0
  320. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/models/session_target.py +0 -0
  321. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/models/snapshot.py +0 -0
  322. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/providers/__init__.py +0 -0
  323. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/providers/aws.py +0 -0
  324. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/providers/hetzner.py +0 -0
  325. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/providers/incus.py +0 -0
  326. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/providers/proxmox.py +0 -0
  327. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/__init__.py +0 -0
  328. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/__init__.py +0 -0
  329. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/hosts.py +0 -0
  330. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/pairing.py +0 -0
  331. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/setup.py +0 -0
  332. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/terminals.py +0 -0
  333. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/app.py +0 -0
  334. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/check.py +0 -0
  335. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/config.py +0 -0
  336. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/discovery.py +0 -0
  337. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/health.py +0 -0
  338. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/logging_config.py +0 -0
  339. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/models.py +0 -0
  340. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/operator_auth.py +0 -0
  341. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/pairing.py +0 -0
  342. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/ssh_master.py +0 -0
  343. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/state.py +0 -0
  344. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/terminal.py +0 -0
  345. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/terminal_registry.py +0 -0
  346. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/src/remo_cli/web/tokens.py +0 -0
  347. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/__init__.py +0 -0
  348. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/ansible/__init__.py +0 -0
  349. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/ansible/test_remo_host_idempotency.py +0 -0
  350. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/conftest.py +0 -0
  351. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/e2e/fixtures.ts +0 -0
  352. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/e2e/ghostty-shell-io.spec.ts +0 -0
  353. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/e2e/ghostty-tui-compatibility.spec.ts +0 -0
  354. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/e2e/keyboard-routing.spec.ts +0 -0
  355. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/e2e/mobile.spec.ts +0 -0
  356. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/e2e/reconnect.spec.ts +0 -0
  357. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/e2e/workspace-layout.spec.ts +0 -0
  358. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/image/__init__.py +0 -0
  359. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/image/test_docker_image.py +0 -0
  360. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/integration/__init__.py +0 -0
  361. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/integration/_nine_terminal_fixture.py +0 -0
  362. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/integration/orbstack-cloud-init.yml +0 -0
  363. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/integration/orbstack.sh +0 -0
  364. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/integration/test_nine_terminals.py +0 -0
  365. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/integration/test_nine_terminals_soak.py +0 -0
  366. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/integration/test_remo_host_e2e.py +0 -0
  367. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/integration/test_security_rejections.py +0 -0
  368. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/integration/test_terminal_attach.py +0 -0
  369. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/integration/test_web_adopt_e2e.py +0 -0
  370. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/integration/test_web_cli_parity.py +0 -0
  371. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/perf/__init__.py +0 -0
  372. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/perf/test_latency.py +0 -0
  373. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/__init__.py +0 -0
  374. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/cli/__init__.py +0 -0
  375. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/cli/providers/test_aws_snapshot.py +0 -0
  376. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/cli/providers/test_hetzner_snapshot.py +0 -0
  377. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/cli/providers/test_incus_snapshot.py +0 -0
  378. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/cli/providers/test_proxmox_snapshot.py +0 -0
  379. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/cli/test_cp.py +0 -0
  380. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/cli/test_main.py +0 -0
  381. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/cli/test_shell.py +0 -0
  382. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/cli/test_web_adopt_cmd.py +0 -0
  383. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/__init__.py +0 -0
  384. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_ansible_runner.py +0 -0
  385. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_config.py +0 -0
  386. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_known_hosts.py +0 -0
  387. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_output.py +0 -0
  388. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_picker.py +0 -0
  389. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_remo_host_client.py +0 -0
  390. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_rsync.py +0 -0
  391. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_snapshot.py +0 -0
  392. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_ssh.py +0 -0
  393. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_ssh_controlpath.py +0 -0
  394. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_ssh_identity_opts.py +0 -0
  395. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_validation.py +0 -0
  396. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_version.py +0 -0
  397. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_adopt_authorize.py +0 -0
  398. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_adopt_code.py +0 -0
  399. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_adopt_payload.py +0 -0
  400. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_adopt_trust.py +0 -0
  401. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_push.py +0 -0
  402. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/models/__init__.py +0 -0
  403. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/models/test_capability.py +0 -0
  404. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/models/test_discovery.py +0 -0
  405. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/models/test_session_target.py +0 -0
  406. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/providers/__init__.py +0 -0
  407. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/providers/test_aws_snapshot.py +0 -0
  408. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/providers/test_hetzner_snapshot.py +0 -0
  409. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/providers/test_incus_snapshot.py +0 -0
  410. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/providers/test_proxmox_devcontainer_runtime.py +0 -0
  411. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/providers/test_proxmox_snapshot.py +0 -0
  412. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/test_ansible_templates.py +0 -0
  413. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/test_host_model.py +0 -0
  414. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/__init__.py +0 -0
  415. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/_pairing_support.py +0 -0
  416. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/conftest.py +0 -0
  417. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_backpressure.py +0 -0
  418. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_check.py +0 -0
  419. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_csp.py +0 -0
  420. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_discovery.py +0 -0
  421. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_health_states.py +0 -0
  422. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_hosts_mapping.py +0 -0
  423. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_lazy_import.py +0 -0
  424. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_log_redaction.py +0 -0
  425. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_mint_gating.py +0 -0
  426. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_operator_auth.py +0 -0
  427. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_pairing.py +0 -0
  428. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_pairing_origin.py +0 -0
  429. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_setup_api.py +0 -0
  430. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_setup_auth.py +0 -0
  431. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_setup_dormancy.py +0 -0
  432. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_state.py +0 -0
  433. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_terminal_resize.py +0 -0
  434. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_terminals_api.py +0 -0
  435. {remo_cli-2.2.0rc5 → remo_cli-2.2.0rc6}/tests/unit/web/test_tokens.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remo-cli
3
- Version: 2.2.0rc5
3
+ Version: 2.2.0rc6
4
4
  Summary: Remote development environment CLI
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -102,8 +102,12 @@ The SPA is a two-pane **web console**:
102
102
  row (or its `+` button) **adds** it to a responsive grid (1/2/3 columns by count). Clicking a grid
103
103
  tile solos it; **Esc** collapses the grid back to the focused terminal; number keys **1–9** jump to
104
104
  the numbered sessions (⌘ 1–9 add to the grid). Hidden terminals stay connected and keep their
105
- scrollback. Each terminal header shows `provider · instance · region`, connection state, and
106
- reconnect/close controls.
105
+ scrollback. Each terminal header shows `provider · instance · region`, connection state, and a
106
+ window-control cluster of the display modes — **▭ Normal** (single view), **⊞ Grid** (the grid, when
107
+ one is available), **⤢ Fullscreen** (the terminal fills the whole window — shell chrome hidden, plus
108
+ best-effort browser fullscreen), and **✕ Close** — with the current mode shown active. Press **f** to
109
+ toggle fullscreen on the focused terminal; **Esc** exits it. Fullscreen is a presentation overlay: it
110
+ never disturbs the single/grid layout underneath, so exiting returns to exactly where you were.
107
111
 
108
112
  **Session-row glyphs** (also shown in the rail legend):
109
113
 
@@ -32,6 +32,13 @@
32
32
  display: flex;
33
33
  }
34
34
 
35
+ /* Fullscreen terminal: the shell collapses to just the pane. The rail/divider/
36
+ * backbar are already withheld from render (see AppShell); here we drop the top
37
+ * bar so the maximized terminal fills the whole viewport. */
38
+ .app-shell--maximized .topbar {
39
+ display: none;
40
+ }
41
+
35
42
  .app-rail {
36
43
  flex: none;
37
44
  flex-direction: column;
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { useCallback, useEffect, useMemo, useState, type PointerEvent } from "react";
7
7
  import type { SessionTarget } from "../api/client";
8
+ import { exitBrowserFullscreen } from "../lib/fullscreen";
8
9
  import { useDiscovery } from "../state/discovery";
9
10
  import { useHealth } from "../state/health";
10
11
  import { settingsActions, useSettings } from "../state/settings";
@@ -42,6 +43,36 @@ export function AppShell(): JSX.Element {
42
43
  return () => window.removeEventListener("resize", onResize);
43
44
  }, []);
44
45
 
46
+ // A terminal is fullscreen when the overlay id still resolves to an attached
47
+ // card; that both hides the shell chrome (class below) and drives browser
48
+ // fullscreen. Entering browser fullscreen is requested from the click/keypress
49
+ // gesture (WorkspacePane / keyboard); here we own the two exit paths:
50
+ // (a) whenever the overlay clears through ANY path, leave browser fullscreen;
51
+ // (b) when the user leaves browser fullscreen natively (Esc/F11), clear the
52
+ // overlay so the shell chrome comes back.
53
+ const maximized =
54
+ workspace.maximizedId !== null && workspace.attached.includes(workspace.maximizedId);
55
+ const restore = workspace.restore;
56
+
57
+ useEffect(() => {
58
+ if (!maximized) {
59
+ exitBrowserFullscreen();
60
+ }
61
+ }, [maximized]);
62
+
63
+ useEffect(() => {
64
+ if (typeof document === "undefined") {
65
+ return undefined;
66
+ }
67
+ const onChange = (): void => {
68
+ if (!document.fullscreenElement && maximized) {
69
+ restore();
70
+ }
71
+ };
72
+ document.addEventListener("fullscreenchange", onChange);
73
+ return () => document.removeEventListener("fullscreenchange", onChange);
74
+ }, [maximized, restore]);
75
+
45
76
  const filters: RailFilters = useMemo(
46
77
  () => ({ search, providerFilter, sessionOnly }),
47
78
  [search, providerFilter, sessionOnly],
@@ -136,9 +167,11 @@ export function AppShell(): JSX.Element {
136
167
  );
137
168
 
138
169
  const paneHasContent = workspace.attached.length > 0;
139
- const railHidden = narrow ? paneHasContent : settings.railCollapsed;
170
+ // Fullscreen collapses the shell to just the maximized terminal: rail and
171
+ // divider gone, top bar hidden by the .app-shell--maximized class.
172
+ const railHidden = maximized || (narrow ? paneHasContent : settings.railCollapsed);
140
173
  const paneHidden = narrow && !paneHasContent;
141
- const showDivider = !narrow && !settings.railCollapsed;
174
+ const showDivider = !narrow && !settings.railCollapsed && !maximized;
142
175
 
143
176
  // "Loading" until the first discovery has produced instances — either before
144
177
  // the first poll (lastRefreshedAt null) or while a refresh is still in flight
@@ -151,7 +184,7 @@ export function AppShell(): JSX.Element {
151
184
  const noCredentials = health.checks.ssh_identity === "missing";
152
185
 
153
186
  return (
154
- <div className="app-shell">
187
+ <div className={`app-shell${maximized ? " app-shell--maximized" : ""}`}>
155
188
  <TopBar
156
189
  showRailToggle={!narrow}
157
190
  railCollapsed={settings.railCollapsed}
@@ -165,7 +198,7 @@ export function AppShell(): JSX.Element {
165
198
  onShortcuts={onToggleShortcuts}
166
199
  />
167
200
 
168
- {discovery.isRefreshing && (
201
+ {discovery.isRefreshing && !maximized && (
169
202
  <div className="app-refresh-bar">
170
203
  <div className="app-refresh-bar-fill" />
171
204
  </div>
@@ -203,7 +236,7 @@ export function AppShell(): JSX.Element {
203
236
  )}
204
237
 
205
238
  <div className="app-pane" style={{ display: paneHidden ? "none" : "flex" }}>
206
- {narrow && paneHasContent && (
239
+ {narrow && paneHasContent && !maximized && (
207
240
  <div className="app-backbar">
208
241
  <button
209
242
  type="button"
@@ -9,7 +9,8 @@ interface ShortcutsModalProps {
9
9
  const SHORTCUTS: { desc: string; key: string }[] = [
10
10
  { desc: "Open session 1–9 (single)", key: "1 – 9" },
11
11
  { desc: "Add session to grid", key: "⌘ 1–9" },
12
- { desc: "Collapse grid to focused", key: "esc" },
12
+ { desc: "Fullscreen focused terminal", key: "f" },
13
+ { desc: "Exit fullscreen / collapse grid", key: "esc" },
13
14
  { desc: "Show this panel", key: "?" },
14
15
  ];
15
16
 
@@ -141,11 +141,49 @@
141
141
  background: var(--bg-btn-hover);
142
142
  }
143
143
 
144
- .terminal-card--grid .tc-btn--close {
144
+ /* Window-control cluster: a tight row of square, icon-only mode buttons. */
145
+ .tc-winctl {
146
+ display: flex;
147
+ align-items: center;
148
+ gap: 5px;
149
+ }
150
+
151
+ .tc-btn--icon {
152
+ width: 26px;
153
+ height: 26px;
154
+ padding: 0;
155
+ display: inline-flex;
156
+ align-items: center;
157
+ justify-content: center;
158
+ font-size: 13px;
159
+ line-height: 1;
160
+ }
161
+
162
+ .terminal-card--grid .tc-btn--icon {
145
163
  width: 22px;
146
164
  height: 22px;
147
- padding: 0;
148
- font-size: 10px;
165
+ font-size: 11px;
166
+ }
167
+
168
+ /* Current display mode: highlighted (and disabled — you can't re-enter it). */
169
+ .tc-btn--active {
170
+ border-color: color-mix(in oklch, var(--accent) 55%, var(--border-strong));
171
+ background: color-mix(in oklch, var(--accent) 18%, transparent);
172
+ color: color-mix(in oklch, var(--accent) 82%, white);
173
+ }
174
+
175
+ /* Unavailable modes (e.g. Grid with no grid to show) read as inert. */
176
+ .tc-btn--icon:disabled {
177
+ cursor: default;
178
+ }
179
+ .tc-btn--icon:disabled:not(.tc-btn--active) {
180
+ opacity: 0.4;
181
+ }
182
+ .tc-btn--icon:disabled:hover {
183
+ background: var(--bg-btn);
184
+ }
185
+ .tc-btn--active:disabled:hover {
186
+ background: color-mix(in oklch, var(--accent) 18%, transparent);
149
187
  }
150
188
 
151
189
  .tc-btn--accent {
@@ -193,9 +231,14 @@
193
231
  padding: 8px 13px;
194
232
  font-size: 13px;
195
233
  }
196
- .terminal-card--grid .tc-btn--close {
197
- width: 32px;
198
- height: 32px;
199
- font-size: 13px;
234
+ .tc-btn--icon,
235
+ .terminal-card--grid .tc-btn--icon {
236
+ width: 34px;
237
+ height: 34px;
238
+ padding: 0;
239
+ font-size: 15px;
240
+ }
241
+ .tc-winctl {
242
+ gap: 7px;
200
243
  }
201
244
  }
@@ -38,6 +38,9 @@ const STATE_LABELS: Record<TerminalConnectionState, string> = {
38
38
  };
39
39
 
40
40
  export type TerminalCardMode = "single" | "grid";
41
+ /** Which of the mutually-exclusive display modes this card is currently in.
42
+ * Drives the window-control cluster's active/disabled state. */
43
+ export type TerminalViewState = "normal" | "grid" | "fullscreen";
41
44
 
42
45
  interface TerminalCardProps {
43
46
  target: SessionTarget;
@@ -50,11 +53,17 @@ interface TerminalCardProps {
50
53
  isFocused: boolean;
51
54
  /** 1-based position label shown on a grid tile. */
52
55
  num?: number;
56
+ /** The display mode this card is currently in (window-control cluster state). */
57
+ viewState: TerminalViewState;
53
58
  onClose: () => void;
54
59
  /** Grid tile clicked → solo it (single view). */
55
60
  onSolo?: () => void;
56
- /** Single view "Back to grid" omitted when there's no grid to return to. */
57
- onBackToGrid?: () => void;
61
+ /** Window-control "Normal": solo this terminal into the single view. */
62
+ onNormal: () => void;
63
+ /** Window-control "Grid": show the grid — omitted when none is available. */
64
+ onGrid?: () => void;
65
+ /** Window-control "Fullscreen": enter fullscreen, or exit if already in it. */
66
+ onToggleFullscreen: () => void;
58
67
  /** Called when the user clicks into the surface (focus this terminal). */
59
68
  onFocusRequest?: () => void;
60
69
  /** Called when output arrives while this card is hidden (rail activity dot). */
@@ -80,9 +89,12 @@ export function TerminalCard({
80
89
  isVisible,
81
90
  isFocused,
82
91
  num,
92
+ viewState,
83
93
  onClose,
84
94
  onSolo,
85
- onBackToGrid,
95
+ onNormal,
96
+ onGrid,
97
+ onToggleFullscreen,
86
98
  onFocusRequest,
87
99
  onActivity,
88
100
  onEnded,
@@ -298,44 +310,81 @@ export function TerminalCard({
298
310
  {STATE_LABELS[connectionState]}
299
311
  </span>
300
312
  <div className="terminal-card-controls">
301
- {mode === "single" && onBackToGrid && (
313
+ {needsManualReconnect && (
302
314
  <button
303
315
  type="button"
304
- className="tc-btn"
305
- title="Return to the grid you came from"
316
+ className="tc-btn tc-btn--accent"
317
+ data-testid={`terminal-reconnect-${target.id}`}
306
318
  onClick={(e) => {
307
319
  e.stopPropagation();
308
- onBackToGrid();
320
+ handleReconnect();
309
321
  }}
310
322
  >
311
- Grid
323
+ Reconnect
312
324
  </button>
313
325
  )}
314
- {needsManualReconnect && (
326
+ {/* Window-control cluster: mutually-exclusive display modes (the
327
+ * current one shown active + disabled) plus close. Fullscreen toggles.
328
+ * Icons only; the label is the tooltip. */}
329
+ <div className="tc-winctl" role="group" aria-label="Terminal display mode">
315
330
  <button
316
331
  type="button"
317
- className="tc-btn tc-btn--accent"
318
- data-testid={`terminal-reconnect-${target.id}`}
332
+ className={`tc-btn tc-btn--icon${viewState === "normal" ? " tc-btn--active" : ""}`}
333
+ data-testid={`terminal-normal-${target.id}`}
334
+ title="Normal (single view)"
335
+ aria-label="Normal view"
336
+ aria-pressed={viewState === "normal"}
337
+ disabled={viewState === "normal"}
319
338
  onClick={(e) => {
320
339
  e.stopPropagation();
321
- handleReconnect();
340
+ onNormal();
322
341
  }}
323
342
  >
324
- ↻ Reconnect
343
+
325
344
  </button>
326
- )}
327
- <button
328
- type="button"
329
- className="tc-btn tc-btn--close"
330
- data-testid={`terminal-close-${target.id}`}
331
- title="Close terminal — remote Zellij session stays alive"
332
- onClick={(e) => {
333
- e.stopPropagation();
334
- handleClose();
335
- }}
336
- >
337
- {mode === "grid" ? "✕" : "Close"}
338
- </button>
345
+ <button
346
+ type="button"
347
+ className={`tc-btn tc-btn--icon${viewState === "grid" ? " tc-btn--active" : ""}`}
348
+ data-testid={`terminal-grid-${target.id}`}
349
+ title="Grid view"
350
+ aria-label="Grid view"
351
+ aria-pressed={viewState === "grid"}
352
+ disabled={viewState === "grid" || !onGrid}
353
+ onClick={(e) => {
354
+ e.stopPropagation();
355
+ onGrid?.();
356
+ }}
357
+ >
358
+
359
+ </button>
360
+ <button
361
+ type="button"
362
+ className={`tc-btn tc-btn--icon${viewState === "fullscreen" ? " tc-btn--active" : ""}`}
363
+ data-testid={`terminal-fullscreen-${target.id}`}
364
+ title={viewState === "fullscreen" ? "Exit fullscreen" : "Fullscreen"}
365
+ aria-label={viewState === "fullscreen" ? "Exit fullscreen" : "Fullscreen"}
366
+ aria-pressed={viewState === "fullscreen"}
367
+ onClick={(e) => {
368
+ e.stopPropagation();
369
+ onToggleFullscreen();
370
+ }}
371
+ >
372
+ {viewState === "fullscreen" ? "⤡" : "⤢"}
373
+ </button>
374
+ <button
375
+ type="button"
376
+ className="tc-btn tc-btn--icon tc-btn--close"
377
+ data-testid={`terminal-close-${target.id}`}
378
+ title="Close terminal — remote Zellij session stays alive"
379
+ aria-label="Close terminal"
380
+ onClick={(e) => {
381
+ e.stopPropagation();
382
+ handleClose();
383
+ }}
384
+ >
385
+
386
+ </button>
387
+ </div>
339
388
  </div>
340
389
  </header>
341
390
 
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { useMemo } from "react";
6
6
  import type { SessionTarget } from "../api/client";
7
+ import { requestBrowserFullscreen } from "../lib/fullscreen";
7
8
  import { useWorkspace } from "../state/workspace";
8
9
  import { TerminalCard } from "./TerminalCard";
9
10
  import "./WorkspacePane.css";
@@ -43,7 +44,7 @@ export function WorkspacePane({
43
44
  narrow,
44
45
  }: WorkspacePaneProps): JSX.Element {
45
46
  const workspace = useWorkspace();
46
- const { attached, visible, focusedId, prevGrid } = workspace;
47
+ const { attached, visible, focusedId, prevGrid, maximizedId } = workspace;
47
48
 
48
49
  // Only attached ids that still resolve to a live target get a card.
49
50
  const attachedTargets = useMemo(
@@ -70,18 +71,40 @@ export function WorkspacePane({
70
71
  );
71
72
  }
72
73
 
73
- const mode = visible.length <= 1 ? "single" : "grid";
74
+ // Fullscreen is an orthogonal overlay: it only takes effect when it still
75
+ // resolves to an attached card. When active, that one card fills the pane and
76
+ // the single↔grid layout underneath is left untouched (so exiting restores it).
77
+ const maximized =
78
+ maximizedId !== null && attached.includes(maximizedId) ? maximizedId : null;
79
+
80
+ // Cards render as single (full-bleed) while a card is maximized; otherwise the
81
+ // usual single↔grid split by how many are visible.
82
+ const mode = maximized || visible.length <= 1 ? "single" : "grid";
74
83
  const cols = gridColumns(visible.length, narrow);
75
84
  const rows = Math.max(1, Math.ceil(visible.length / cols));
76
- const canBackToGrid = (prevGrid ?? []).filter((id) => attached.includes(id)).length > 1;
85
+ // The Grid control is available when a grid can be shown — either the visible
86
+ // set is already a grid (fullscreen opened over one) or a grid was remembered.
87
+ const canGrid =
88
+ visible.filter((id) => attached.includes(id)).length > 1 ||
89
+ (prevGrid ?? []).filter((id) => attached.includes(id)).length > 1;
77
90
 
78
91
  // 1-based position for each visible id (grid tile number badge).
79
92
  const visibleIndex = new Map(visible.map((id, i) => [id, i + 1]));
80
93
 
94
+ const toggleFullscreen = (id: string): void => {
95
+ if (maximized === id) {
96
+ workspace.restore();
97
+ } else {
98
+ workspace.maximize(id);
99
+ // Request from within this click gesture so the browser allows it.
100
+ requestBrowserFullscreen();
101
+ }
102
+ };
103
+
81
104
  return (
82
105
  <main className="workspace" data-testid="workspace">
83
106
  <div
84
- className={`workspace-body workspace-body--${mode}`}
107
+ className={`workspace-body workspace-body--${mode}${maximized ? " workspace-body--maximized" : ""}`}
85
108
  style={
86
109
  mode === "grid"
87
110
  ? {
@@ -92,7 +115,8 @@ export function WorkspacePane({
92
115
  }
93
116
  >
94
117
  {attachedTargets.map((target) => {
95
- const isVisible = visible.includes(target.id);
118
+ const isVisible = maximized ? target.id === maximized : visible.includes(target.id);
119
+ const viewState = maximized === target.id ? "fullscreen" : mode === "grid" ? "grid" : "normal";
96
120
  return (
97
121
  <TerminalCard
98
122
  key={target.id}
@@ -102,9 +126,12 @@ export function WorkspacePane({
102
126
  isVisible={isVisible}
103
127
  isFocused={focusedId === target.id}
104
128
  num={visibleIndex.get(target.id)}
129
+ viewState={viewState}
105
130
  onClose={() => workspace.closeTerm(target.id)}
106
131
  onSolo={mode === "grid" ? () => workspace.soloTile(target.id) : undefined}
107
- onBackToGrid={mode === "single" && canBackToGrid ? workspace.backToGrid : undefined}
132
+ onNormal={() => workspace.soloTile(target.id)}
133
+ onGrid={canGrid ? workspace.backToGrid : undefined}
134
+ onToggleFullscreen={() => toggleFullscreen(target.id)}
108
135
  onFocusRequest={() => workspace.setFocused(target.id)}
109
136
  onActivity={() => workspace.markUnread(target.id)}
110
137
  onEnded={() => onTerminalEnded(target)}
@@ -0,0 +1,38 @@
1
+ // Best-effort browser (F11-style) fullscreen helpers.
2
+ //
3
+ // These are a thin, defensive wrapper over the Fullscreen API. The web-app's
4
+ // "fullscreen" terminal mode is primarily an *app-viewport* takeover (the shell
5
+ // hides its chrome via a CSS class keyed on `workspace.maximizedId`); requesting
6
+ // true browser fullscreen on top of that is a best-effort enhancement:
7
+ //
8
+ // - `requestBrowserFullscreen()` MUST be called from within a user-gesture
9
+ // handler (a click or keydown) — browsers reject it otherwise. On rejection
10
+ // the app-viewport takeover still stands, so the caller needs no fallback.
11
+ // - Exiting is centralized in AppShell (an effect keyed on `maximizedId`), so
12
+ // every exit path — the restore button, `f`, Esc, closing the card, or
13
+ // opening another session — funnels through `exitBrowserFullscreen()`.
14
+ //
15
+ // Both calls are idempotent: they guard on `document.fullscreenElement`, so
16
+ // double-invoking (e.g. our exit effect firing after a native Esc already left
17
+ // fullscreen) is a no-op and never loops.
18
+
19
+ export function requestBrowserFullscreen(): void {
20
+ if (typeof document === "undefined") {
21
+ return;
22
+ }
23
+ const el = document.documentElement;
24
+ if (!document.fullscreenElement && el.requestFullscreen) {
25
+ // Swallow rejections: no gesture / disallowed by the browser just means we
26
+ // stay in the app-viewport takeover.
27
+ void el.requestFullscreen().catch(() => {});
28
+ }
29
+ }
30
+
31
+ export function exitBrowserFullscreen(): void {
32
+ if (typeof document === "undefined") {
33
+ return;
34
+ }
35
+ if (document.fullscreenElement && document.exitFullscreen) {
36
+ void document.exitFullscreen().catch(() => {});
37
+ }
38
+ }
@@ -2,14 +2,16 @@
2
2
  //
3
3
  // 1–9 open that numbered session solo (single view)
4
4
  // ⌘/Ctrl/Shift 1–9 add/toggle that session in the grid
5
- // Esc close an open overlay, else collapse the grid to the
6
- // focused terminal
5
+ // f toggle fullscreen on the focused terminal
6
+ // Esc close an open overlay, else exit fullscreen, else collapse
7
+ // the grid to the focused terminal
7
8
  // ? toggle the shortcuts panel
8
9
  //
9
10
  // Ignored while typing in an <input>/<textarea> (e.g. the rail search).
10
11
 
11
12
  import { useEffect } from "react";
12
13
  import type { SessionTarget } from "../api/client";
14
+ import { requestBrowserFullscreen } from "../lib/fullscreen";
13
15
  import type { UseWorkspaceResult } from "./workspace";
14
16
 
15
17
  export interface ConsoleKeyboardConfig {
@@ -45,6 +47,11 @@ export function useConsoleKeyboard(config: ConsoleKeyboardConfig): void {
45
47
  if (onEscapeOverlay()) {
46
48
  return;
47
49
  }
50
+ // Exit fullscreen before touching the single/grid layout.
51
+ if (workspace.maximizedId) {
52
+ workspace.restore();
53
+ return;
54
+ }
48
55
  // Collapse a grid to just the focused terminal.
49
56
  if (workspace.visible.length > 1 && workspace.focusedId) {
50
57
  workspace.soloTile(workspace.focusedId);
@@ -52,6 +59,23 @@ export function useConsoleKeyboard(config: ConsoleKeyboardConfig): void {
52
59
  return;
53
60
  }
54
61
 
62
+ // Fullscreen the focused terminal (toggle). Requesting browser fullscreen
63
+ // here works because a keydown is a user gesture.
64
+ if (e.key === "f" || e.key === "F") {
65
+ const id = workspace.focusedId;
66
+ if (!id) {
67
+ return;
68
+ }
69
+ e.preventDefault();
70
+ if (workspace.maximizedId === id) {
71
+ workspace.restore();
72
+ } else {
73
+ workspace.maximize(id);
74
+ requestBrowserFullscreen();
75
+ }
76
+ return;
77
+ }
78
+
55
79
  if (/^[1-9]$/.test(e.key)) {
56
80
  const target = flatOpenable[Number(e.key) - 1];
57
81
  if (!target) {