cortexshift 0.1.0__py3-none-any.whl
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.
- cortexshift/__init__.py +10 -0
- cortexshift/__main__.py +6 -0
- cortexshift/adapters/__init__.py +22 -0
- cortexshift/adapters/command_runner.py +116 -0
- cortexshift/adapters/discovery.py +55 -0
- cortexshift/adapters/git/__init__.py +10 -0
- cortexshift/adapters/git/inspector.py +321 -0
- cortexshift/adapters/git/parser.py +140 -0
- cortexshift/adapters/headless_runner.py +92 -0
- cortexshift/adapters/process_runner.py +56 -0
- cortexshift/adapters/providers/__init__.py +4 -0
- cortexshift/adapters/providers/antigravity.py +530 -0
- cortexshift/adapters/providers/claude.py +375 -0
- cortexshift/adapters/providers/codex.py +434 -0
- cortexshift/adapters/sqlite/__init__.py +10 -0
- cortexshift/adapters/sqlite/migrations.py +268 -0
- cortexshift/adapters/sqlite/store.py +914 -0
- cortexshift/adapters/workspace_lease.py +123 -0
- cortexshift/application/__init__.py +42 -0
- cortexshift/application/checkpoint_builder.py +218 -0
- cortexshift/application/checkpoint_service.py +273 -0
- cortexshift/application/doctor.py +80 -0
- cortexshift/application/handoff_builder.py +281 -0
- cortexshift/application/handoff_renderer.py +430 -0
- cortexshift/application/handoff_service.py +66 -0
- cortexshift/application/init_service.py +86 -0
- cortexshift/application/locator.py +48 -0
- cortexshift/application/native_session.py +65 -0
- cortexshift/application/recovery_service.py +235 -0
- cortexshift/application/repository_service.py +146 -0
- cortexshift/application/resume_service.py +124 -0
- cortexshift/application/run_service.py +270 -0
- cortexshift/application/session_launcher.py +183 -0
- cortexshift/application/session_service.py +63 -0
- cortexshift/application/source_session.py +62 -0
- cortexshift/application/status_service.py +73 -0
- cortexshift/application/switch_service.py +671 -0
- cortexshift/application/task_service.py +201 -0
- cortexshift/application/task_workspace.py +152 -0
- cortexshift/cli/__init__.py +5 -0
- cortexshift/cli/app.py +2477 -0
- cortexshift/domain/__init__.py +153 -0
- cortexshift/domain/checkpoint.py +174 -0
- cortexshift/domain/doctor.py +68 -0
- cortexshift/domain/errors.py +277 -0
- cortexshift/domain/git.py +102 -0
- cortexshift/domain/handoff.py +241 -0
- cortexshift/domain/identifiers.py +27 -0
- cortexshift/domain/launch.py +58 -0
- cortexshift/domain/mcp_binding.py +81 -0
- cortexshift/domain/native_session.py +19 -0
- cortexshift/domain/project.py +37 -0
- cortexshift/domain/provider.py +67 -0
- cortexshift/domain/session.py +92 -0
- cortexshift/domain/status.py +40 -0
- cortexshift/domain/task.py +191 -0
- cortexshift/mcp/__init__.py +38 -0
- cortexshift/mcp/context.py +165 -0
- cortexshift/mcp/facade.py +513 -0
- cortexshift/mcp/models.py +178 -0
- cortexshift/mcp/resources.py +45 -0
- cortexshift/mcp/server.py +52 -0
- cortexshift/mcp/tools.py +176 -0
- cortexshift/ports/__init__.py +39 -0
- cortexshift/ports/checkpoint_store.py +45 -0
- cortexshift/ports/command_runner.py +56 -0
- cortexshift/ports/discovery.py +41 -0
- cortexshift/ports/handoff_delivery.py +91 -0
- cortexshift/ports/handoff_store.py +43 -0
- cortexshift/ports/headless_runner.py +58 -0
- cortexshift/ports/native_session.py +20 -0
- cortexshift/ports/process_runner.py +31 -0
- cortexshift/ports/provider.py +152 -0
- cortexshift/ports/repository.py +44 -0
- cortexshift/ports/session_store.py +27 -0
- cortexshift/ports/state_store.py +55 -0
- cortexshift/ports/workspace_lease.py +39 -0
- cortexshift/tui/__init__.py +24 -0
- cortexshift/tui/actions.py +58 -0
- cortexshift/tui/app.py +1051 -0
- cortexshift/tui/coordinator.py +173 -0
- cortexshift/tui/cortexshift.tcss +258 -0
- cortexshift/tui/facade.py +614 -0
- cortexshift/tui/modals.py +594 -0
- cortexshift/tui/models.py +503 -0
- cortexshift/tui/screens/__init__.py +81 -0
- cortexshift/tui/screens/checkpoints.py +188 -0
- cortexshift/tui/screens/handoffs.py +180 -0
- cortexshift/tui/screens/help.py +117 -0
- cortexshift/tui/screens/overview.py +200 -0
- cortexshift/tui/screens/providers.py +169 -0
- cortexshift/tui/screens/repository.py +143 -0
- cortexshift/tui/screens/sessions.py +146 -0
- cortexshift/tui/screens/task.py +174 -0
- cortexshift/tui/widgets.py +209 -0
- cortexshift-0.1.0.dist-info/METADATA +202 -0
- cortexshift-0.1.0.dist-info/RECORD +100 -0
- cortexshift-0.1.0.dist-info/WHEEL +4 -0
- cortexshift-0.1.0.dist-info/entry_points.txt +2 -0
- cortexshift-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Runs the dashboard, then hands the terminal to a native provider.
|
|
2
|
+
|
|
3
|
+
The ordering enforced here is an architectural guarantee, not a convenience:
|
|
4
|
+
|
|
5
|
+
CortexShiftApp.run() → returns TuiExitRequest
|
|
6
|
+
(Textual has torn down and restored the terminal)
|
|
7
|
+
→ RunService / ResumeService / SwitchService
|
|
8
|
+
→ native provider owns the terminal
|
|
9
|
+
|
|
10
|
+
No provider process is ever started while the Textual application is alive, and no
|
|
11
|
+
provider TUI is embedded, captured, or multiplexed. `launch_provider` is only ever
|
|
12
|
+
reached after `run_dashboard` has returned.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from cortexshift.application.resume_service import ResumeService
|
|
21
|
+
from cortexshift.application.run_service import RunService
|
|
22
|
+
from cortexshift.application.switch_service import SwitchService
|
|
23
|
+
from cortexshift.domain.errors import ProjectNotInitializedError, TerminalRequiredError
|
|
24
|
+
from cortexshift.domain.session import Session
|
|
25
|
+
from cortexshift.tui.actions import TuiExitAction, TuiExitRequest
|
|
26
|
+
from cortexshift.tui.app import CortexShiftApp, build_app
|
|
27
|
+
|
|
28
|
+
TTY_REQUIRED_MESSAGE = (
|
|
29
|
+
"The CortexShift dashboard requires an interactive terminal (TTY).\n\n"
|
|
30
|
+
"Both standard input and standard output must be attached to a terminal."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _silent(message: str) -> None:
|
|
35
|
+
"""Default announcement sink: the dashboard says nothing on its own."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def terminal_is_interactive() -> bool:
|
|
39
|
+
"""Whether both stdin and stdout are attached to a terminal."""
|
|
40
|
+
try:
|
|
41
|
+
return sys.stdin.isatty() and sys.stdout.isatty()
|
|
42
|
+
except Exception:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class TuiSession:
|
|
48
|
+
"""Result of one dashboard invocation and any provider launch that followed."""
|
|
49
|
+
|
|
50
|
+
request: TuiExitRequest | None = None
|
|
51
|
+
session: Session | None = None
|
|
52
|
+
launched: bool = False
|
|
53
|
+
events: list[str] = field(default_factory=list)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def exit_code(self) -> int:
|
|
57
|
+
"""Process exit code derived from the launched provider session, if any."""
|
|
58
|
+
if self.session is None:
|
|
59
|
+
return 0
|
|
60
|
+
from cortexshift.domain.session import SessionStatus
|
|
61
|
+
|
|
62
|
+
if self.session.status in (SessionStatus.COMPLETED, SessionStatus.INTERRUPTED):
|
|
63
|
+
return 0
|
|
64
|
+
return self.session.exit_code or 1
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class TuiCoordinator:
|
|
68
|
+
"""Sequences dashboard lifetime and native provider launch."""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
*,
|
|
73
|
+
run_service: RunService | None = None,
|
|
74
|
+
resume_service: ResumeService | None = None,
|
|
75
|
+
switch_service: SwitchService | None = None,
|
|
76
|
+
app_factory: Callable[[], CortexShiftApp] | None = None,
|
|
77
|
+
dashboard_runner: Callable[[], TuiExitRequest | None] | None = None,
|
|
78
|
+
is_tty: Callable[[], bool] = terminal_is_interactive,
|
|
79
|
+
announce: Callable[[str], None] | None = None,
|
|
80
|
+
) -> None:
|
|
81
|
+
self._run_service = run_service
|
|
82
|
+
self._resume_service = resume_service
|
|
83
|
+
self._switch_service = switch_service
|
|
84
|
+
self._app_factory = app_factory
|
|
85
|
+
self._dashboard_runner = dashboard_runner
|
|
86
|
+
self._is_tty = is_tty
|
|
87
|
+
self._announce = announce or _silent
|
|
88
|
+
|
|
89
|
+
# ------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
def start(self, project_root: Path | str | None = None) -> TuiSession:
|
|
92
|
+
"""Run the dashboard and, if requested, launch a provider afterwards.
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
ProjectNotInitializedError: If no initialized project is found.
|
|
96
|
+
TerminalRequiredError: If the process has no interactive terminal.
|
|
97
|
+
"""
|
|
98
|
+
result = TuiSession()
|
|
99
|
+
|
|
100
|
+
request = self._run_dashboard(project_root)
|
|
101
|
+
result.events.append("dashboard_finished")
|
|
102
|
+
result.request = request
|
|
103
|
+
|
|
104
|
+
if request is None:
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
self._announce(f"{request.description} — CortexShift released the terminal.")
|
|
108
|
+
result.events.append("provider_launch_started")
|
|
109
|
+
result.launched = True
|
|
110
|
+
result.session = self.launch_provider(request)
|
|
111
|
+
result.events.append("provider_launch_finished")
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
def _run_dashboard(self, project_root: Path | str | None) -> TuiExitRequest | None:
|
|
115
|
+
"""Run the Textual application to completion and return its exit request.
|
|
116
|
+
|
|
117
|
+
The project is resolved before the terminal is checked: someone standing in the
|
|
118
|
+
wrong directory should be told that, not told about their terminal.
|
|
119
|
+
"""
|
|
120
|
+
if self._dashboard_runner is not None:
|
|
121
|
+
return self._dashboard_runner()
|
|
122
|
+
|
|
123
|
+
resolved = resolve_project_root(project_root)
|
|
124
|
+
|
|
125
|
+
if not self._is_tty():
|
|
126
|
+
raise TerminalRequiredError(TTY_REQUIRED_MESSAGE)
|
|
127
|
+
|
|
128
|
+
app = self._app_factory() if self._app_factory else build_app(resolved)
|
|
129
|
+
return app.run()
|
|
130
|
+
|
|
131
|
+
# ------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
def launch_provider(self, request: TuiExitRequest) -> Session | None:
|
|
134
|
+
"""Invoke the existing provider service for a dashboard exit request.
|
|
135
|
+
|
|
136
|
+
Only ever called after the Textual application has finished. The provider owns
|
|
137
|
+
the terminal from here; CortexShift does not wrap, capture, or emulate it.
|
|
138
|
+
"""
|
|
139
|
+
if request.action is TuiExitAction.RUN:
|
|
140
|
+
service = self._run_service or RunService()
|
|
141
|
+
return service.run(provider_name=request.provider)
|
|
142
|
+
|
|
143
|
+
if request.action is TuiExitAction.RESUME:
|
|
144
|
+
service_resume = self._resume_service or ResumeService()
|
|
145
|
+
outcome = service_resume.resume(
|
|
146
|
+
provider_name=request.provider,
|
|
147
|
+
session_id=request.selected_session_id,
|
|
148
|
+
)
|
|
149
|
+
return outcome if isinstance(outcome, Session) else None
|
|
150
|
+
|
|
151
|
+
service_switch = self._switch_service or SwitchService()
|
|
152
|
+
switch_result = service_switch.switch(
|
|
153
|
+
target_provider_name=request.provider,
|
|
154
|
+
new_session=request.force_new_session,
|
|
155
|
+
resume_session_id=request.selected_session_id,
|
|
156
|
+
note=request.note,
|
|
157
|
+
)
|
|
158
|
+
return switch_result.target_session
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def resolve_project_root(start_dir: Path | str | None = None) -> Path:
|
|
162
|
+
"""Resolve the initialized project the dashboard should bind to.
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
ProjectNotInitializedError: If no initialized project root is found.
|
|
166
|
+
"""
|
|
167
|
+
from cortexshift.application.locator import ProjectLocator
|
|
168
|
+
|
|
169
|
+
start_path = Path(start_dir) if start_dir is not None else None
|
|
170
|
+
project_root = ProjectLocator.find_project_root(start_path)
|
|
171
|
+
if project_root is None:
|
|
172
|
+
raise ProjectNotInitializedError()
|
|
173
|
+
return project_root
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/* CortexShift control center theme.
|
|
2
|
+
*
|
|
3
|
+
* A restrained engineering palette: dark ground, one accent, subtle borders, and
|
|
4
|
+
* status emphasis reserved for state that actually needs attention. Meaning is never
|
|
5
|
+
* carried by colour alone — glyphs accompany every status marker.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
Screen {
|
|
9
|
+
background: $surface;
|
|
10
|
+
color: $foreground;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* ---------- Shell layout ---------- */
|
|
14
|
+
|
|
15
|
+
#shell {
|
|
16
|
+
height: 1fr;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#sidebar {
|
|
20
|
+
width: 20;
|
|
21
|
+
min-width: 20;
|
|
22
|
+
background: $panel;
|
|
23
|
+
border-right: solid $primary-darken-2;
|
|
24
|
+
padding: 1 1 0 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#sidebar-title {
|
|
28
|
+
color: $accent;
|
|
29
|
+
text-style: bold;
|
|
30
|
+
padding: 0 0 1 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#nav {
|
|
34
|
+
height: auto;
|
|
35
|
+
border: none;
|
|
36
|
+
background: $panel;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#nav > .option-list--option {
|
|
40
|
+
padding: 0 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#nav:focus > .option-list--option-highlighted {
|
|
44
|
+
background: $primary;
|
|
45
|
+
color: $text;
|
|
46
|
+
text-style: bold;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#content {
|
|
50
|
+
padding: 1 2;
|
|
51
|
+
height: 1fr;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#status-line {
|
|
55
|
+
height: 1;
|
|
56
|
+
padding: 0 2;
|
|
57
|
+
background: $panel;
|
|
58
|
+
color: $text-muted;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Compact layout for terminals narrower than 90 columns: the sidebar folds away and
|
|
62
|
+
the section name moves into the status line. Number keys keep working. */
|
|
63
|
+
|
|
64
|
+
.compact #sidebar {
|
|
65
|
+
display: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.compact #content {
|
|
69
|
+
padding: 1 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ---------- Sections ---------- */
|
|
73
|
+
|
|
74
|
+
.section {
|
|
75
|
+
height: 1fr;
|
|
76
|
+
overflow-y: auto;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.section-title {
|
|
80
|
+
text-style: bold;
|
|
81
|
+
color: $accent;
|
|
82
|
+
padding: 0 0 1 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.panel {
|
|
86
|
+
height: auto;
|
|
87
|
+
border-top: solid $primary-darken-2;
|
|
88
|
+
border-title-color: $accent;
|
|
89
|
+
border-title-style: bold;
|
|
90
|
+
border-subtitle-color: $text-muted;
|
|
91
|
+
border-subtitle-style: italic;
|
|
92
|
+
padding: 0 0 1 0;
|
|
93
|
+
margin: 1 0 0 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.panel-title {
|
|
97
|
+
color: $text-muted;
|
|
98
|
+
text-style: bold;
|
|
99
|
+
padding: 1 0 0 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.field-list {
|
|
103
|
+
height: auto;
|
|
104
|
+
padding: 0 0 0 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.bullets {
|
|
108
|
+
height: auto;
|
|
109
|
+
padding: 0 0 0 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.columns {
|
|
113
|
+
height: auto;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.column {
|
|
117
|
+
width: 1fr;
|
|
118
|
+
height: auto;
|
|
119
|
+
padding: 0 2 0 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.empty-state {
|
|
123
|
+
padding: 2 2;
|
|
124
|
+
color: $text-muted;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.notice {
|
|
128
|
+
padding: 0 0 1 0;
|
|
129
|
+
color: $warning;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ---------- Tables ---------- */
|
|
133
|
+
|
|
134
|
+
DataTable {
|
|
135
|
+
height: auto;
|
|
136
|
+
max-height: 20;
|
|
137
|
+
border: round $primary-darken-2;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
DataTable > .datatable--header {
|
|
141
|
+
text-style: bold;
|
|
142
|
+
background: $panel;
|
|
143
|
+
color: $text-muted;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
DataTable > .datatable--cursor {
|
|
147
|
+
background: $primary;
|
|
148
|
+
color: $text;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.detail {
|
|
152
|
+
height: auto;
|
|
153
|
+
border-top: solid $primary-darken-2;
|
|
154
|
+
padding: 1 0 0 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* ---------- Status emphasis ---------- */
|
|
158
|
+
|
|
159
|
+
.state-ok {
|
|
160
|
+
color: $success;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.state-warn {
|
|
164
|
+
color: $warning;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.state-fail {
|
|
168
|
+
color: $error;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.state-active {
|
|
172
|
+
color: $accent;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.state-muted {
|
|
176
|
+
color: $text-muted;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* ---------- Modals ---------- */
|
|
180
|
+
|
|
181
|
+
ModalScreen {
|
|
182
|
+
align: center middle;
|
|
183
|
+
background: $surface 60%;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.modal {
|
|
187
|
+
width: 72;
|
|
188
|
+
max-width: 90%;
|
|
189
|
+
height: auto;
|
|
190
|
+
max-height: 90%;
|
|
191
|
+
padding: 1 2;
|
|
192
|
+
background: $panel;
|
|
193
|
+
border: thick $primary;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.modal-title {
|
|
197
|
+
text-style: bold;
|
|
198
|
+
color: $accent;
|
|
199
|
+
padding: 0 0 1 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.modal-help {
|
|
203
|
+
color: $text-muted;
|
|
204
|
+
padding: 0 0 1 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.modal-error {
|
|
208
|
+
color: $error;
|
|
209
|
+
height: auto;
|
|
210
|
+
padding: 0 0 1 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.modal-body {
|
|
214
|
+
height: auto;
|
|
215
|
+
max-height: 24;
|
|
216
|
+
overflow-y: auto;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.modal-buttons {
|
|
220
|
+
height: auto;
|
|
221
|
+
align-horizontal: right;
|
|
222
|
+
padding: 1 0 0 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.modal-buttons Button {
|
|
226
|
+
margin: 0 0 0 2;
|
|
227
|
+
min-width: 12;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.field-label {
|
|
231
|
+
color: $text-muted;
|
|
232
|
+
padding: 1 0 0 0;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
Input {
|
|
236
|
+
border: tall $primary-darken-2;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
Input:focus {
|
|
240
|
+
border: tall $accent;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* ---------- Too-small terminal guard ---------- */
|
|
244
|
+
|
|
245
|
+
#too-small {
|
|
246
|
+
display: none;
|
|
247
|
+
height: 1fr;
|
|
248
|
+
padding: 1 2;
|
|
249
|
+
color: $warning;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.too-small #shell {
|
|
253
|
+
display: none;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.too-small #too-small {
|
|
257
|
+
display: block;
|
|
258
|
+
}
|