labnotes 0.0.81__tar.gz → 0.0.82__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 (146) hide show
  1. {labnotes-0.0.81 → labnotes-0.0.82}/PKG-INFO +1 -1
  2. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/group_runs.py +6 -1
  3. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/list_runs.py +4 -1
  4. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/load_experiment.py +2 -0
  5. labnotes-0.0.82/src/labnotes/static/elapsed.js +18 -0
  6. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/static/labnotes.css +2 -0
  7. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/templates/experiment.html +4 -3
  8. {labnotes-0.0.81 → labnotes-0.0.82}/tests/integration/conftest.py +20 -0
  9. {labnotes-0.0.81 → labnotes-0.0.82}/.gitignore +0 -0
  10. {labnotes-0.0.81 → labnotes-0.0.82}/CHANGELOG.md +0 -0
  11. {labnotes-0.0.81 → labnotes-0.0.82}/MIGRATIONS.md +0 -0
  12. {labnotes-0.0.81 → labnotes-0.0.82}/README.md +0 -0
  13. {labnotes-0.0.81 → labnotes-0.0.82}/pyproject.toml +0 -0
  14. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/__init__.py +0 -0
  15. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/__main__.py +0 -0
  16. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/asset_url.py +0 -0
  17. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/check_counts.py +0 -0
  18. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/cli.py +0 -0
  19. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/context.md +0 -0
  20. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/context_guide.py +0 -0
  21. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/count_groups.py +0 -0
  22. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/count_runs.py +0 -0
  23. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/create_app.py +0 -0
  24. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/create_experiment.py +0 -0
  25. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/create_note.py +0 -0
  26. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/__init__.py +0 -0
  27. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/control_run.py +0 -0
  28. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/daemon_app.py +0 -0
  29. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/daemon_client.py +0 -0
  30. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/daemon_running.py +0 -0
  31. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/daemon_server.py +0 -0
  32. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/describe_status.py +0 -0
  33. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/ensure_daemon.py +0 -0
  34. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/exit_when_idle.py +0 -0
  35. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/remote_start.py +0 -0
  36. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/run_daemon.py +0 -0
  37. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/run_logs.py +0 -0
  38. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/serve_daemon.py +0 -0
  39. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/socket_path.py +0 -0
  40. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/stop_runs.py +0 -0
  41. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/watch_daemon.py +0 -0
  42. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon/watch_status.py +0 -0
  43. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/daemon_command.py +0 -0
  44. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/data_html.py +0 -0
  45. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/default_config_text.py +0 -0
  46. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/default_counts.py +0 -0
  47. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/drain_watch.py +0 -0
  48. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/experiment_notes.py +0 -0
  49. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/experiment_runs.py +0 -0
  50. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/experiments.py +0 -0
  51. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/file_changes.py +0 -0
  52. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/format_duration.py +0 -0
  53. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/format_started.py +0 -0
  54. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/format_value.py +0 -0
  55. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/init.py +0 -0
  56. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/invocation_started.py +0 -0
  57. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/is_positive_int.py +0 -0
  58. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/list_experiments.py +0 -0
  59. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/load_config.py +0 -0
  60. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/load_run.py +0 -0
  61. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/models.py +0 -0
  62. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/modes.py +0 -0
  63. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/nests_runs.py +0 -0
  64. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/note_path.py +0 -0
  65. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/notebook_kernel.py +0 -0
  66. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/output_html.py +0 -0
  67. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/parse_run_view.py +0 -0
  68. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/print_rows.py +0 -0
  69. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/python_blocks.py +0 -0
  70. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/read_config.py +0 -0
  71. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/read_path.py +0 -0
  72. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/render_markdown.py +0 -0
  73. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/run_command.py +0 -0
  74. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/run_control.py +0 -0
  75. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/run_output.py +0 -0
  76. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/run_status.py +0 -0
  77. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/__init__.py +0 -0
  78. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/describe_event.py +0 -0
  79. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/execute_run.py +0 -0
  80. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/hold_folder.py +0 -0
  81. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/new_param_run_id.py +0 -0
  82. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/new_run_id.py +0 -0
  83. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  84. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/param_args.py +0 -0
  85. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/param_grid.py +0 -0
  86. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/param_run_name.py +0 -0
  87. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/plan_runs.py +0 -0
  88. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/prepare_experiment.py +0 -0
  89. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/read_experiment_config.py +0 -0
  90. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/run_experiment.py +0 -0
  91. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/run_folder.py +0 -0
  92. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/run_headless.py +0 -0
  93. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/runs_parent.py +0 -0
  94. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/shell_command.py +0 -0
  95. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/signal_tree.py +0 -0
  96. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/stop_then_kill.py +0 -0
  97. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/wait_for_tree.py +0 -0
  98. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/runner/write_run_json.py +0 -0
  99. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/save_conflict_copy.py +0 -0
  100. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/save_note.py +0 -0
  101. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/saved_outputs.py +0 -0
  102. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/serve_app.py +0 -0
  103. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/server.py +0 -0
  104. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/shared_params.py +0 -0
  105. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/sort_href.py +0 -0
  106. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/sql_quote.py +0 -0
  107. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/static/experiment.js +0 -0
  108. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/static/local-time.js +0 -0
  109. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/static/new-note.js +0 -0
  110. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/static/note-edit.js +0 -0
  111. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/static/note-editor.js +0 -0
  112. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/static/row-link.js +0 -0
  113. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/static/run-cell.js +0 -0
  114. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/static/run-output.js +0 -0
  115. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/static/view-toggle.js +0 -0
  116. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/summarize_config.py +0 -0
  117. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/__init__.py +0 -0
  118. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/experiment_rows.py +0 -0
  119. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/experiment_run_ids.py +0 -0
  120. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/file_prefix.py +0 -0
  121. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/file_text.py +0 -0
  122. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/iso_time.py +0 -0
  123. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/list_experiment_runs.py +0 -0
  124. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/note_text.py +0 -0
  125. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/open_tables.py +0 -0
  126. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/path_glob.py +0 -0
  127. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/path_pattern.py +0 -0
  128. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/read_experiment.py +0 -0
  129. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/read_file.py +0 -0
  130. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/read_log.py +0 -0
  131. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/read_run.py +0 -0
  132. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tables/summarize_experiments.py +0 -0
  133. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/templates/base.html +0 -0
  134. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/templates/editor_imports.html +0 -0
  135. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/templates/index.html +0 -0
  136. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/templates/note.html +0 -0
  137. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/templates/run.html +0 -0
  138. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tui/__init__.py +0 -0
  139. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tui/dim.py +0 -0
  140. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tui/format_elapsed.py +0 -0
  141. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tui/run_app.py +0 -0
  142. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tui/run_tui.py +0 -0
  143. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/tui/runs_table.py +0 -0
  144. {labnotes-0.0.81 → labnotes-0.0.82}/src/labnotes/wait_for_note.py +0 -0
  145. {labnotes-0.0.81 → labnotes-0.0.82}/testing-conventions.toml +0 -0
  146. {labnotes-0.0.81 → labnotes-0.0.82}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.81
3
+ Version: 0.0.82
4
4
  Summary: Browse experiment designs, notes, and run data from a folder.
5
5
  Project-URL: Repository, https://github.com/thekevinscott/labnotes
6
6
  Author-email: Kevin Scott <me@thekevinscott.com>
@@ -25,7 +25,12 @@ def group_runs(
25
25
  "total": totals.get(name, len(members)),
26
26
  "shared": shared_params(members, params),
27
27
  "passed": sum(1 for run in members if run["labnotes_completed"]),
28
- "failed": sum(1 for run in members if not run["labnotes_completed"]),
28
+ "failed": sum(
29
+ 1
30
+ for run in members
31
+ if not run["labnotes_completed"] and not run["labnotes_unfinished"]
32
+ ),
33
+ "unfinished": sum(1 for run in members if run["labnotes_unfinished"]),
29
34
  }
30
35
  for name, members in sorted(groups.items(), reverse=True)
31
36
  ]
@@ -3,6 +3,8 @@ from dirsql import DirSQL
3
3
  from labnotes.models import RunView
4
4
  from labnotes.sql_quote import quote_identifier, quote_string
5
5
 
6
+ UNFINISHED = "(ended IS NULL AND exit_code IS NULL AND error IS NULL)"
7
+
6
8
 
7
9
  async def list_runs(
8
10
  runs: DirSQL, source: str, completed_run: str, view: RunView
@@ -30,6 +32,7 @@ async def list_runs(
30
32
  else:
31
33
  order = "created DESC, id DESC"
32
34
  return await runs.query(
33
- f"SELECT ({completed_run}) AS labnotes_completed, * FROM {source}"
35
+ f"SELECT ({completed_run}) AS labnotes_completed, {UNFINISHED} AS labnotes_unfinished,"
36
+ f" * FROM {source}"
34
37
  f"{filtered} ORDER BY {order}"
35
38
  )
@@ -8,6 +8,7 @@ from dirsql import DirSQL
8
8
  from labnotes.check_counts import check_counts
9
9
  from labnotes.count_groups import count_groups
10
10
  from labnotes.count_runs import count_runs
11
+ from labnotes.daemon.daemon_running import daemon_running
11
12
  from labnotes.experiment_notes import experiment_notes
12
13
  from labnotes.experiment_runs import experiment_runs
13
14
  from labnotes.format_value import format_value
@@ -82,6 +83,7 @@ async def load_experiment(
82
83
  for note in notes
83
84
  ],
84
85
  "runs_list": runs,
86
+ "runs_live": await daemon_running(root),
85
87
  "run_groups": groups,
86
88
  "run_columns": shown,
87
89
  "run_view": view,
@@ -0,0 +1,18 @@
1
+ const format = (seconds) => {
2
+ const minutes = Math.floor(seconds / 60);
3
+ const hours = Math.floor(minutes / 60);
4
+ const pad = (n) => String(n).padStart(2, "0");
5
+ if (hours) return `${hours}h ${pad(minutes % 60)}m`;
6
+ if (minutes) return `${minutes}m ${pad(seconds % 60)}s`;
7
+ return `${seconds}s`;
8
+ };
9
+
10
+ const tick = () => {
11
+ for (const cell of document.querySelectorAll(".elapsed[data-since]")) {
12
+ const seconds = Math.max(0, Math.floor((Date.now() - Date.parse(cell.dataset.since)) / 1000));
13
+ cell.textContent = format(seconds);
14
+ }
15
+ };
16
+
17
+ tick();
18
+ setInterval(tick, 1000);
@@ -359,6 +359,8 @@ h3 {
359
359
  .status { font-size: 0.8rem; font-weight: 700; }
360
360
  .status.good { color: var(--good); }
361
361
  .status.bad { color: var(--bad); }
362
+ .status.interrupted, .group-row .interrupted { color: var(--ink-3); }
363
+ .status.running, .group-row .running { color: var(--accent); }
362
364
  .counts-error { color: var(--bad); }
363
365
 
364
366
  /* Index view toggle */
@@ -82,8 +82,8 @@
82
82
  </thead>
83
83
  {% macro run_rows(run) %}
84
84
  <tr class="run-row">
85
- <td><span class="status {{ 'good' if run.labnotes_completed else 'bad' }}" title="{{ 'complete' if run.labnotes_completed else 'not complete' }}">{{ '✓' if run.labnotes_completed else '✕' }}</span></td>
86
- {% for column in e.run_columns %}{% if column == 'id' %}<td class="mono"><a class="run-link" href="{{ url_for('run', name=e.name, run=run.id) }}">{{ run.id.partition('/')[2] or run.id if nested else run.id }}</a></td>{% if nested %}<td></td>{% endif %}{% else %}<td class="{{ 'mono' if loop.first }}{{ 'num' if column == 'duration' }}">{{ run[column] | duration if column == 'duration' else (run[column] | started if column == 'started' else run[column] | value) }}</td>{% endif %}{% endfor %}
85
+ <td>{% if run.labnotes_unfinished and e.runs_live %}<span class="status running" title="running">◐</span>{% elif run.labnotes_unfinished %}<span class="status interrupted" title="interrupted">–</span>{% else %}<span class="status {{ 'good' if run.labnotes_completed else 'bad' }}" title="{{ 'complete' if run.labnotes_completed else 'not complete' }}">{{ '✓' if run.labnotes_completed else '✕' }}</span>{% endif %}</td>
86
+ {% for column in e.run_columns %}{% if column == 'id' %}<td class="mono"><a class="run-link" href="{{ url_for('run', name=e.name, run=run.id) }}">{{ run.id.partition('/')[2] or run.id if nested else run.id }}</a></td>{% if nested %}<td></td>{% endif %}{% else %}<td class="{{ 'mono' if loop.first }}{{ 'num' if column == 'duration' }}">{% if column == 'duration' and run.labnotes_unfinished and e.runs_live %}<span class="elapsed" data-since="{{ run.created }}">—</span>{% else %}{{ run[column] | duration if column == 'duration' else (run[column] | started if column == 'started' else run[column] | value) }}{% endif %}</td>{% endif %}{% endfor %}
87
87
  </tr>
88
88
  {% endmacro %}
89
89
  {% if nested %}
@@ -93,7 +93,7 @@
93
93
  {% else %}
94
94
  <tbody class="group{{ ' open' if narrowed }}" data-name="{{ group.name }}">
95
95
  <tr class="group-row">
96
- <td class="num" title="{{ group.passed }} passed, {{ group.failed }} failed"><span class="good">{{ group.passed }}✓</span> <span class="bad">{{ group.failed }}✕</span></td>
96
+ <td class="num" title="{{ group.passed }} passed, {{ group.failed }} failed{% if group.unfinished %}, {{ group.unfinished }} {{ 'running' if e.runs_live else 'interrupted' }}{% endif %}"><span class="good">{{ group.passed }}✓</span> <span class="bad">{{ group.failed }}✕</span>{% if group.unfinished %} <span class="{{ 'running' if e.runs_live else 'interrupted' }}">{{ group.unfinished }}{{ '◐' if e.runs_live else '–' }}</span>{% endif %}</td>
97
97
  <td class="mono"><button type="button" class="group-toggle" aria-expanded="{{ 'true' if narrowed else 'false' }}">{{ group.name }}</button></td>
98
98
  <td class="num">{{ group.runs | length }}{% if narrowed %} of {{ group.total }}{% endif %}</td>
99
99
  {% for column in e.run_columns[1:] %}<td>{% if column == 'started' %}{{ group.started | started if group.started }}{% elif column in group.shared %}{{ group.shared[column] | value }}{% endif %}</td>{% endfor %}
@@ -122,4 +122,5 @@
122
122
  <script src="{{ asset('experiment.js') }}" defer></script>
123
123
  <script src="{{ asset('local-time.js') }}" defer></script>
124
124
  <script src="{{ asset('row-link.js') }}" defer></script>
125
+ <script src="{{ asset('elapsed.js') }}" defer></script>
125
126
  {% endblock %}
@@ -10,6 +10,8 @@ from pathlib import Path
10
10
  import pytest
11
11
  from playwright.async_api import Page, async_playwright
12
12
 
13
+ from labnotes.daemon.socket_path import socket_path
14
+
13
15
  CONFIG = (
14
16
  'name = "Alpha study"\ncommand = "go"\nrepeats = 2\nmode = "fill"\n'
15
17
  '[params]\nsource-language = ["en", "fr"]\n'
@@ -43,6 +45,24 @@ def free_port() -> int:
43
45
  return int(sock.getsockname()[1])
44
46
 
45
47
 
48
+ def daemon(root: Path) -> Iterator[None]:
49
+ labnotes = Path(sys.executable).parent / "labnotes"
50
+ process = subprocess.Popen(
51
+ [labnotes, "-c", root / ".labnotes.toml", "daemon"],
52
+ stdout=subprocess.DEVNULL,
53
+ stderr=subprocess.DEVNULL,
54
+ )
55
+ try:
56
+ for _ in range(100):
57
+ if socket_path(root).exists():
58
+ break
59
+ time.sleep(0.1)
60
+ yield
61
+ finally:
62
+ process.terminate()
63
+ process.wait()
64
+
65
+
46
66
  def serve(root: Path) -> Iterator[str]:
47
67
  port = free_port()
48
68
  labnotes = Path(sys.executable).parent / "labnotes"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes