speclogician 0.0.0b1__py3-none-any.whl → 0.0.0.dev1__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.
Files changed (153) hide show
  1. speclogician/agent/funcs.py +29 -0
  2. speclogician/cmd/agent_cmd.py +89 -0
  3. speclogician/cmd/data_cmd.py +24 -0
  4. speclogician/cmd/model_cmd.py +42 -0
  5. speclogician/cmd/overlay_cmd.py +30 -0
  6. speclogician/cmd/scenario_cmd.py +61 -0
  7. speclogician/cmd/state_cmd.py +52 -0
  8. speclogician/data/artifact.py +8 -50
  9. speclogician/data/container.py +18 -384
  10. speclogician/data/mapping.py +18 -17
  11. speclogician/data/refs.py +12 -11
  12. speclogician/data/reports.py +11 -0
  13. speclogician/data/traces.py +15 -6
  14. speclogician/llms/llmtools.py +102 -0
  15. speclogician/llms/overlay.py +264 -0
  16. speclogician/main.py +36 -102
  17. speclogician/modeling/__init__.py +0 -31
  18. speclogician/modeling/component.py +4 -60
  19. speclogician/modeling/conflict.py +5 -19
  20. speclogician/modeling/domain.py +93 -280
  21. speclogician/modeling/model.py +206 -0
  22. speclogician/modeling/predicates.py +20 -22
  23. speclogician/modeling/report.py +33 -0
  24. speclogician/modeling/scenario.py +119 -87
  25. speclogician/sl_cmd.py +76 -0
  26. speclogician/state/change.py +98 -378
  27. speclogician/state/state.py +183 -399
  28. speclogician/tui/box.tcss +10 -0
  29. speclogician/tui/tui.py +131 -0
  30. speclogician/utils/__init__.py +1 -70
  31. speclogician/utils/imx.py +195 -0
  32. speclogician/utils/load.py +25 -147
  33. speclogician/utils/prompt.md +1 -325
  34. speclogician-0.0.0.dev1.dist-info/METADATA +21 -0
  35. speclogician-0.0.0.dev1.dist-info/RECORD +43 -0
  36. speclogician/commands/__init__.py +0 -15
  37. speclogician/commands/cmd_ch.py +0 -616
  38. speclogician/commands/cmd_find.py +0 -256
  39. speclogician/commands/cmd_view.py +0 -202
  40. speclogician/commands/runner.py +0 -149
  41. speclogician/commands/utils.py +0 -101
  42. speclogician/demos/.DS_Store +0 -0
  43. speclogician/demos/cmd_demo.py +0 -278
  44. speclogician/demos/loader.py +0 -135
  45. speclogician/demos/model.py +0 -27
  46. speclogician/demos/runner.py +0 -51
  47. speclogician/logic/__init__.py +0 -11
  48. speclogician/logic/api/__init__.py +0 -29
  49. speclogician/logic/api/client.py +0 -606
  50. speclogician/logic/api/decomp.py +0 -67
  51. speclogician/logic/api/scenario.py +0 -102
  52. speclogician/logic/api/traces.py +0 -59
  53. speclogician/logic/lib/__init__.py +0 -19
  54. speclogician/logic/lib/complement.py +0 -107
  55. speclogician/logic/lib/domain_model.py +0 -59
  56. speclogician/logic/lib/predicates.py +0 -151
  57. speclogician/logic/lib/scenarios.py +0 -369
  58. speclogician/logic/lib/traces.py +0 -114
  59. speclogician/logic/lib/transitions.py +0 -104
  60. speclogician/logic/main.py +0 -246
  61. speclogician/logic/strings.py +0 -194
  62. speclogician/logic/utils.py +0 -135
  63. speclogician/modeling/complement.py +0 -104
  64. speclogician/modeling/spec.py +0 -306
  65. speclogician/modeling/spec_stats.py +0 -39
  66. speclogician/presentation/api.py +0 -244
  67. speclogician/presentation/builders/_links.py +0 -44
  68. speclogician/presentation/builders/container.py +0 -53
  69. speclogician/presentation/builders/data_artifact.py +0 -42
  70. speclogician/presentation/builders/domain.py +0 -54
  71. speclogician/presentation/builders/instances_list.py +0 -38
  72. speclogician/presentation/builders/predicate.py +0 -51
  73. speclogician/presentation/builders/recommendations.py +0 -41
  74. speclogician/presentation/builders/scenario.py +0 -41
  75. speclogician/presentation/builders/scenario_complement.py +0 -82
  76. speclogician/presentation/builders/smart_find.py +0 -39
  77. speclogician/presentation/builders/spec.py +0 -39
  78. speclogician/presentation/builders/state_diff.py +0 -150
  79. speclogician/presentation/builders/state_instance.py +0 -42
  80. speclogician/presentation/builders/state_instance_summary.py +0 -84
  81. speclogician/presentation/builders/trace.py +0 -58
  82. speclogician/presentation/ctx.py +0 -38
  83. speclogician/presentation/models/container.py +0 -44
  84. speclogician/presentation/models/data_artifact.py +0 -33
  85. speclogician/presentation/models/domain.py +0 -50
  86. speclogician/presentation/models/instances_list.py +0 -23
  87. speclogician/presentation/models/predicate.py +0 -60
  88. speclogician/presentation/models/recommendations.py +0 -34
  89. speclogician/presentation/models/scenario.py +0 -31
  90. speclogician/presentation/models/scenario_complement.py +0 -40
  91. speclogician/presentation/models/smart_find.py +0 -34
  92. speclogician/presentation/models/spec.py +0 -32
  93. speclogician/presentation/models/state_diff.py +0 -34
  94. speclogician/presentation/models/state_instance.py +0 -31
  95. speclogician/presentation/models/state_instance_summary.py +0 -102
  96. speclogician/presentation/models/trace.py +0 -42
  97. speclogician/presentation/preview/__init__.py +0 -13
  98. speclogician/presentation/preview/cli.py +0 -50
  99. speclogician/presentation/preview/fixtures/__init__.py +0 -205
  100. speclogician/presentation/preview/fixtures/artifact_container.py +0 -150
  101. speclogician/presentation/preview/fixtures/data_artifact.py +0 -144
  102. speclogician/presentation/preview/fixtures/domain_model.py +0 -162
  103. speclogician/presentation/preview/fixtures/instances_list.py +0 -162
  104. speclogician/presentation/preview/fixtures/predicate.py +0 -184
  105. speclogician/presentation/preview/fixtures/scenario.py +0 -84
  106. speclogician/presentation/preview/fixtures/scenario_complement.py +0 -81
  107. speclogician/presentation/preview/fixtures/smart_find.py +0 -140
  108. speclogician/presentation/preview/fixtures/spec.py +0 -95
  109. speclogician/presentation/preview/fixtures/state_diff.py +0 -158
  110. speclogician/presentation/preview/fixtures/state_instance.py +0 -128
  111. speclogician/presentation/preview/fixtures/state_instance_summary.py +0 -80
  112. speclogician/presentation/preview/fixtures/trace.py +0 -206
  113. speclogician/presentation/preview/registry.py +0 -42
  114. speclogician/presentation/renderers/__init__.py +0 -24
  115. speclogician/presentation/renderers/container.py +0 -136
  116. speclogician/presentation/renderers/data_artifact.py +0 -144
  117. speclogician/presentation/renderers/domain.py +0 -123
  118. speclogician/presentation/renderers/instances_list.py +0 -120
  119. speclogician/presentation/renderers/predicate.py +0 -180
  120. speclogician/presentation/renderers/recommendations.py +0 -90
  121. speclogician/presentation/renderers/scenario.py +0 -94
  122. speclogician/presentation/renderers/scenario_complement.py +0 -59
  123. speclogician/presentation/renderers/smart_find.py +0 -307
  124. speclogician/presentation/renderers/spec.py +0 -105
  125. speclogician/presentation/renderers/state_diff.py +0 -102
  126. speclogician/presentation/renderers/state_instance.py +0 -82
  127. speclogician/presentation/renderers/state_instance_summary.py +0 -143
  128. speclogician/presentation/renderers/trace.py +0 -122
  129. speclogician/shell/app.py +0 -170
  130. speclogician/shell/shell_ch.py +0 -263
  131. speclogician/shell/shell_view.py +0 -153
  132. speclogician/state/change_result.py +0 -32
  133. speclogician/state/diff.py +0 -191
  134. speclogician/state/inst.py +0 -574
  135. speclogician/state/recommendation.py +0 -13
  136. speclogician/state/recommender.py +0 -577
  137. speclogician/state/state_stats.py +0 -133
  138. speclogician/tui/__init__.py +0 -0
  139. speclogician/tui/app.py +0 -257
  140. speclogician/tui/app.tcss +0 -160
  141. speclogician/tui/demo.py +0 -45
  142. speclogician/tui/images/speclogician-full.png +0 -0
  143. speclogician/tui/images/speclogician-minimal.png +0 -0
  144. speclogician/tui/main_screen.py +0 -454
  145. speclogician/tui/splash_screen.py +0 -51
  146. speclogician/tui/stats_screen.py +0 -125
  147. speclogician/utils/testing.py +0 -151
  148. speclogician-0.0.0b1.dist-info/METADATA +0 -116
  149. speclogician-0.0.0b1.dist-info/RECORD +0 -139
  150. /speclogician/{presentation → agent}/__init__.py +0 -0
  151. /speclogician/{presentation/builders → cmd}/__init__.py +0 -0
  152. /speclogician/{presentation/models → llms}/__init__.py +0 -0
  153. {speclogician-0.0.0b1.dist-info → speclogician-0.0.0.dev1.dist-info}/WHEEL +0 -0
@@ -1,133 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/state/state_stats.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- from dataclasses import dataclass
10
-
11
- from .inst import StateInstance
12
-
13
-
14
- @dataclass(frozen=True)
15
- class StatsCalculator:
16
- """
17
- Compute time-series stats over a collection of State objects.
18
-
19
- Assumptions:
20
- - Each State has `instances` ordered newest-first (index 0 = latest).
21
- - We return lists oldest-first for plotting.
22
- """
23
-
24
- @staticmethod
25
- def stats(instances: list[StateInstance]) -> dict[str, list[int | str]]:
26
- """
27
- Note: we reverse the order of StateInstances here so the 0s element (latest state instance) is all the way on the right
28
- """
29
- out: dict[str, list[int | str]] = {
30
- # identity / time
31
- "created_at": [],
32
- "num_changes": [],
33
-
34
- # ---- domain model counts (from DomainModel) ----
35
- "num_state_preds_total": [],
36
- "num_state_preds_valid_logic": [],
37
- "num_state_preds_matched": [],
38
-
39
- "num_action_preds_total": [],
40
- "num_action_preds_valid_logic": [],
41
- "num_action_preds_matched": [],
42
-
43
- "num_preds_total": [],
44
- "num_preds_valid_logic": [],
45
- "num_preds_matched": [],
46
-
47
- "num_trans_total": [],
48
- "num_trans_valid_logic": [],
49
- "num_trans_matched": [],
50
-
51
- # ---- scenario stats (from Spec) ----
52
- "num_sc_total": [],
53
- "num_sc_missing": [],
54
- "num_sc_matched": [],
55
- "num_sc_inconsistent": [],
56
- "num_sc_conflicted": [],
57
-
58
- # ---- artifact stats (from ArtifactContainer) ----
59
- "num_test_traces_total": [],
60
- "num_test_traces_matched": [],
61
- "num_test_traces_logic_good": [],
62
-
63
- "num_log_traces_total": [],
64
- "num_log_traces_matched": [],
65
- "num_log_traces_logic_good": [],
66
-
67
- "num_doc_arts_total": [],
68
- "num_doc_arts_matched": [],
69
- "num_src_code_arts_total": [],
70
- "num_src_code_arts_matched": [],
71
-
72
- # ---- optional derived metrics (handy for “progress”) ----
73
- "num_traces_total": [],
74
- "num_traces_logic_good": [],
75
- "num_traces_matched": [],
76
- }
77
-
78
- for inst in instances[::-1]:
79
- # identity / time
80
- out["created_at"].append(inst.created_at.isoformat())
81
- out["num_changes"].append(len(inst.changes or []))
82
-
83
- # domain model
84
- dm = inst.spec.domain_model
85
- out["num_state_preds_total"].append(dm.num_state_preds_total)
86
- out["num_state_preds_valid_logic"].append(dm.num_state_preds_valid_logic)
87
- out["num_state_preds_matched"].append(dm.num_state_preds_matched)
88
-
89
- out["num_action_preds_total"].append(dm.num_action_preds_total)
90
- out["num_action_preds_valid_logic"].append(dm.num_action_preds_valid_logic)
91
- out["num_action_preds_matched"].append(dm.num_action_preds_matched)
92
-
93
- out["num_preds_total"].append(dm.num_preds_total)
94
- out["num_preds_valid_logic"].append(dm.num_preds_valid_logic)
95
- out["num_preds_matched"].append(dm.num_preds_matched)
96
-
97
- out["num_trans_total"].append(dm.num_trans_total)
98
- out["num_trans_valid_logic"].append(dm.num_trans_valid_logic)
99
- out["num_trans_matched"].append(dm.num_trans_matched)
100
-
101
- # spec / scenarios
102
- sp = inst.spec
103
- out["num_sc_total"].append(sp.num_sc_total)
104
- out["num_sc_missing"].append(sp.num_sc_missing)
105
- out["num_sc_matched"].append(sp.num_sc_matched)
106
- out["num_sc_inconsistent"].append(sp.num_sc_inconsistent)
107
- out["num_sc_conflicted"].append(sp.num_sc_conflicted)
108
-
109
- # artifacts
110
- ac = inst.art_container
111
- out["num_test_traces_total"].append(ac.num_test_traces_total)
112
- out["num_test_traces_matched"].append(ac.num_test_traces_matched)
113
- out["num_test_traces_logic_good"].append(ac.num_test_traces_logic_good)
114
-
115
- out["num_log_traces_total"].append(ac.num_log_traces_total)
116
- out["num_log_traces_matched"].append(ac.num_log_traces_matched)
117
- out["num_log_traces_logic_good"].append(ac.num_log_traces_logic_good)
118
-
119
- out["num_doc_arts_total"].append(ac.num_doc_arts_total)
120
- out["num_doc_arts_matched"].append(ac.num_doc_arts_matched)
121
- out["num_src_code_arts_total"].append(ac.num_src_code_arts_total)
122
- out["num_src_code_arts_matched"].append(ac.num_src_code_arts_matched)
123
-
124
- # derived
125
- traces_total = ac.num_test_traces_total + ac.num_log_traces_total
126
- traces_good = ac.num_test_traces_logic_good + ac.num_log_traces_logic_good
127
- traces_matched = ac.num_test_traces_matched + ac.num_log_traces_matched
128
-
129
- out["num_traces_total"].append(traces_total)
130
- out["num_traces_logic_good"].append(traces_good)
131
- out["num_traces_matched"].append(traces_matched)
132
-
133
- return out
File without changes
speclogician/tui/app.py DELETED
@@ -1,257 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/tui/app.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- import os
10
- import time
11
- from datetime import datetime, timezone
12
- from pathlib import Path
13
- from typing import Any
14
-
15
- import pyperclip
16
- from textual.app import App
17
- from textual.screen import Screen
18
-
19
- from ..state.state import State
20
- from ..utils.load import load_state
21
- from .demo import DemoScreen
22
- from .splash_screen import SplashScreen
23
- from .stats_screen import StatsScreen
24
- from .main_screen import MainScreen
25
-
26
-
27
- class SpecLogicianApp(App[Any]):
28
- """SpecLogician TUI app with optional file-watch + manual update."""
29
-
30
- TITLE = "Imandra SpecLogician"
31
- SUB_TITLE = "AI-powered spec synthesis, verification & analysis"
32
-
33
- CSS_PATH = "app.tcss"
34
-
35
- SCREENS = {
36
- "splash": SplashScreen,
37
- "demo": DemoScreen,
38
- "stats": StatsScreen,
39
- "main": MainScreen,
40
- }
41
-
42
- BINDINGS = [
43
- ("s", "stats", "Statistics"),
44
- ("m", "main", "Main"),
45
- ("d", "demo", "Demo Artifacts"),
46
- ("c", "copy_full_iml", "Copy IML"),
47
- ("u", "update_state", "Update State"),
48
- ("q", "quit", "Quit"),
49
- ]
50
-
51
- def __init__(
52
- self,
53
- state: State | None = None,
54
- demo_md: str | None = None,
55
- demo_title: str = "Demo artifacts",
56
- *,
57
- state_path: Path | None = None,
58
- watch_state_file: bool = False,
59
- poll_interval_s: float = 0.5,
60
- ) -> None:
61
- super().__init__()
62
-
63
- self.demo_md = demo_md
64
- self.demo_title = demo_title
65
-
66
- self.state_path = state_path
67
- self.watch_state_file = watch_state_file
68
- self.poll_interval_s = poll_interval_s
69
-
70
- # manual-update mode fields
71
- self._pending_state_sig: tuple[int, int] | None = None
72
- self._update_available: bool = False
73
-
74
- # state
75
- self.state = load_state() if state is None else state
76
-
77
- # file watch bookkeeping
78
- self._state_sig: tuple[int, int] | None = None
79
- self._last_reload_ts: float = 0.0
80
-
81
- # one-shot popup
82
- self._notified_update_available: bool = False
83
-
84
- # -------------------------------------------------------------------------
85
- # mount + nav
86
- # -------------------------------------------------------------------------
87
-
88
- def on_mount(self) -> None:
89
- # If demo isn't available,
90
- # action_demo() will show a popup instead.
91
-
92
- # start watch timer if enabled
93
- if self.watch_state_file and self.state_path:
94
- self._state_sig = self._get_state_file_signature()
95
- self._set_last_updated_indicator(self._state_sig)
96
- self.set_interval(self.poll_interval_s, self._poll_state_file)
97
- else:
98
- self._set_last_updated_indicator(None)
99
-
100
- self.push_screen("splash")
101
-
102
- def action_stats(self) -> None:
103
- self.switch_screen("stats")
104
-
105
- def action_main(self) -> None:
106
- self.switch_screen("main")
107
-
108
- def action_demo(self) -> None:
109
- # If not in demo mode, show a popup (mirrors "No update available")
110
- if not self.demo_md:
111
- self.notify("No demo artifacts available (run in demo mode to enable).", timeout=2.0)
112
- return
113
- self.push_screen(DemoScreen(self.demo_md, title=self.demo_title))
114
-
115
- def action_noop(self) -> None:
116
- pass
117
-
118
- # -------------------------------------------------------------------------
119
- # copy helper
120
- # -------------------------------------------------------------------------
121
-
122
- def action_copy_full_iml(self) -> None:
123
- screen: Screen[Any] = self.screen
124
- if not isinstance(screen, MainScreen):
125
- self.notify("Copy is only available on the Main screen.", timeout=1.0)
126
- return
127
-
128
- iml_text = getattr(screen, "_full_iml_text", "")
129
- if not iml_text:
130
- self.notify("No IML model to copy.", timeout=1.0)
131
- return
132
-
133
- pyperclip.copy(iml_text)
134
- self.notify("Full IML model copied to clipboard.", timeout=1.2)
135
-
136
- # -------------------------------------------------------------------------
137
- # state apply / refresh
138
- # -------------------------------------------------------------------------
139
-
140
- def _apply_new_state(self, new_state: State) -> None:
141
- self.state = new_state
142
-
143
- screen: Screen[Any] = self.screen
144
- if isinstance(screen, MainScreen):
145
- screen.refresh_from_state()
146
-
147
- # -------------------------------------------------------------------------
148
- # file watching (polling) + subtitle indicator
149
- # -------------------------------------------------------------------------
150
-
151
- def _fmt_mtime(self, mtime_ns: int) -> str:
152
- dt = datetime.fromtimestamp(mtime_ns / 1e9, tz=timezone.utc)
153
- return dt.strftime("%Y-%m-%d %H:%M:%S UTC")
154
-
155
- def _set_last_updated_indicator(self, sig: tuple[int, int] | None) -> None:
156
- base = "AI-powered spec synthesis, verification & analysis"
157
- if not self.watch_state_file or not self.state_path or sig is None:
158
- self.sub_title = base
159
- return
160
- mtime_ns, _ = sig
161
- self.sub_title = f"{base} • state updated {self._fmt_mtime(mtime_ns)}"
162
-
163
- def _set_update_indicator_pending(self, sig: tuple[int, int]) -> None:
164
- base = "AI-powered spec synthesis, verification & analysis"
165
- mtime_ns, _ = sig
166
- self.sub_title = f"{base} • update available ({self._fmt_mtime(mtime_ns)})"
167
-
168
- def _get_state_file_signature(self) -> tuple[int, int] | None:
169
- if not self.state_path:
170
- return None
171
- try:
172
- st = os.stat(self.state_path)
173
- return (st.st_mtime_ns, st.st_size)
174
- except FileNotFoundError:
175
- return None
176
-
177
- def _poll_state_file(self) -> None:
178
- if not self.watch_state_file or not self.state_path:
179
- return
180
-
181
- sig = self._get_state_file_signature()
182
- if sig is None:
183
- return
184
-
185
- if self._state_sig is None:
186
- self._state_sig = sig
187
- self._set_last_updated_indicator(sig)
188
- return
189
-
190
- if sig == self._state_sig:
191
- return
192
-
193
- # file changed: mark pending update (manual)
194
- self._pending_state_sig = sig
195
- self._update_available = True
196
- self._set_update_indicator_pending(sig)
197
-
198
- # update footer status if MainScreen is visible
199
- screen: Screen[Any] = self.screen
200
- if isinstance(screen, MainScreen):
201
- mtime_ns, _ = sig
202
- screen.set_status(
203
- f"[yellow]Update available[/yellow] — press [bold]u[/bold] to reload ({self._fmt_mtime(mtime_ns)})"
204
- )
205
-
206
- # one-time popup hint
207
- if not self._notified_update_available:
208
- self._notified_update_available = True
209
- self.notify("Update available — press 'u' to reload.", timeout=2.0)
210
-
211
- # -------------------------------------------------------------------------
212
- # manual update action
213
- # -------------------------------------------------------------------------
214
-
215
- def action_update_state(self) -> None:
216
- if not self._update_available or not self.state_path or not self._pending_state_sig:
217
- self.notify("No update available.", timeout=1.0)
218
- return
219
-
220
- # Debounce: avoid repeated clicks in quick succession
221
- now = time.time()
222
- if now - self._last_reload_ts < 0.25:
223
- return
224
-
225
- try:
226
- data = self.state_path.read_text(encoding="utf-8", errors="replace")
227
- new_state = State.model_validate_json(data) # type: ignore[attr-defined]
228
- except Exception:
229
- self.notify(
230
- "Failed to load updated state (file may be mid-write).",
231
- severity="warning",
232
- timeout=1.5,
233
- )
234
- return
235
-
236
- self._last_reload_ts = now
237
-
238
- # accept update
239
- self._state_sig = self._pending_state_sig
240
- self._pending_state_sig = None
241
- self._update_available = False
242
-
243
- # update subtitle back to last-updated
244
- self._set_last_updated_indicator(self._state_sig)
245
-
246
- # apply + refresh UI
247
- self._apply_new_state(new_state)
248
-
249
- # allow the popup again next time a new update arrives
250
- self._notified_update_available = False
251
-
252
- # clear footer status if on main
253
- screen: Screen[Any] = self.screen
254
- if isinstance(screen, MainScreen):
255
- screen.clear_status()
256
-
257
- self.notify("State updated.", timeout=1.2)
speclogician/tui/app.tcss DELETED
@@ -1,160 +0,0 @@
1
- /* Root screen should have real height */
2
- Screen {
3
- height: 100%;
4
- }
5
-
6
- #status_line {
7
- dock: bottom;
8
- height: auto;
9
- padding: 0 1;
10
- color: $text-muted;
11
- background: $panel;
12
- }
13
-
14
- /* -------------------------------
15
- Top toolbar (Update button)
16
- -------------------------------- */
17
-
18
- #top_toolbar {
19
- height: auto;
20
- width: 100%;
21
- padding: 0 1;
22
- margin-bottom: 1;
23
- align: left middle;
24
- }
25
-
26
- #update_badge {
27
- padding-left: 1;
28
- color: $text-muted;
29
- }
30
-
31
- /* -------------------------------
32
- Body layout
33
- -------------------------------- */
34
-
35
- #body {
36
- width: 100%;
37
- height: 100%;
38
- layout: vertical;
39
- }
40
-
41
- #instances_split {
42
- width: 100%;
43
- height: 1fr;
44
- layout: horizontal;
45
- }
46
-
47
- /* -------------------------------
48
- Left: instances panel
49
- -------------------------------- */
50
-
51
- #instances_panel {
52
- width: 80; /* adjust as needed */
53
- height: 1fr;
54
- overflow: hidden; /* key: prevents visual spill */
55
- }
56
-
57
- #instances_list {
58
- width: 100%;
59
- height: 1fr;
60
- border: solid gray round;
61
- overflow: hidden; /* also key */
62
- }
63
-
64
- /* -------------------------------
65
- Instance list rows + badges
66
- -------------------------------- */
67
-
68
- .inst_row {
69
- width: 100%;
70
- height: auto;
71
- }
72
-
73
- .inst_when {
74
- width: 1fr;
75
- padding: 0 1;
76
- }
77
-
78
- .inst_badges {
79
- width: auto;
80
- height: auto;
81
- align: right middle;
82
- }
83
-
84
- /* badge “pill” look */
85
- .inst_badge {
86
- padding: 0 1;
87
- margin-left: 1;
88
- text-style: bold;
89
- /* If your Textual version supports it, these make it look nice.
90
- If any of these error, remove them. */
91
- border: round $panel;
92
- background: $panel;
93
- color: $text-muted;
94
- }
95
-
96
- /* severity colors (foreground) */
97
- .inst_badge--error {
98
- color: $error;
99
- }
100
-
101
- .inst_badge--warning {
102
- color: $warning;
103
- }
104
-
105
- .inst_badge--ok {
106
- color: $success;
107
- }
108
-
109
- .inst_badge--info {
110
- color: $accent;
111
- }
112
-
113
- .inst_badge--muted {
114
- color: $text-muted;
115
- }
116
-
117
- /* -------------------------------
118
- Right: tabbed content
119
- -------------------------------- */
120
-
121
- TabbedContent {
122
- width: 1fr;
123
- height: 1fr;
124
- }
125
-
126
- TabPane {
127
- width: 1fr;
128
- height: 1fr;
129
- }
130
-
131
- /* -------------------------------
132
- RichLog content
133
- -------------------------------- */
134
-
135
- RichLog {
136
- width: 100%;
137
- height: 1fr;
138
- }
139
-
140
- /* -------------------------------
141
- Full IML panel
142
- -------------------------------- */
143
-
144
- #full_iml_panel {
145
- width: 1fr;
146
- height: 1fr;
147
- layout: vertical;
148
- }
149
-
150
- #full_iml_toolbar {
151
- height: auto;
152
- width: 100%;
153
- padding: 0 1;
154
- align: left middle;
155
- }
156
-
157
- #full_iml_model {
158
- width: 100%;
159
- height: 1fr;
160
- }
speclogician/tui/demo.py DELETED
@@ -1,45 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/tui/demo.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- from typing import Any
10
- from textual.screen import Screen
11
- from textual.app import ComposeResult
12
- from textual.widgets import Header, Footer, RichLog
13
-
14
- from rich.markdown import Markdown
15
- from rich.panel import Panel
16
-
17
-
18
- class DemoScreen(Screen[Any]):
19
- """Shows demo collateral (artifact markdown) before entering the main TUI."""
20
-
21
- CSS = """
22
- #mdlog { height: 1fr; width: 100%; }
23
- """
24
-
25
- def __init__(self, markdown_text: str, *, title: str = "Demo artifacts") -> None:
26
- super().__init__()
27
- self._md_text = markdown_text
28
- self._title = title
29
-
30
- def compose(self) -> ComposeResult:
31
- yield Header()
32
- self.mdlog = RichLog(id="mdlog", wrap=True, highlight=True, markup=False, auto_scroll=False)
33
- yield self.mdlog
34
- yield Footer()
35
-
36
- def on_mount(self) -> None:
37
- # Render markdown inside a panel and write it as a single renderable.
38
- self.mdlog.clear()
39
- self.mdlog.write(
40
- Panel(
41
- Markdown(self._md_text or "_(empty)_"),
42
- title=f"[bold]{self._title}[/bold]",
43
- border_style="magenta",
44
- )
45
- )