Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/); versioning is [SemVer](

- sl-viewer history_tab property surface (WBS-6.2 #444): `crates/sl-viewer/tests/properties_viewer_history.rs` adds 15 proptest properties — `history_tab::to_timeline_entry` carries `summary.id` / `summary.title` / `summary.message_count` / `summary.intent_state` (= `Extracted`), `corpus`, and `cwd` through unchanged; `message_previews` is capped at 3 (empty when input has no messages); `total_messages` matches input. `unfinished` is `false` for empty sessions, `false` when the last message content (case-insensitive) contains one of the six documented done-phrases ("looks good", "approved", "ship it", "all good", "thanks", "done"), and `true` otherwise. `to_timeline_entry` is deterministic. `all_timeline_entries` produces one entry per input session, sorts by `total_messages` descending (newest-first), every session id appears exactly once, and is deterministic.

- sl-viewer tokens SSOT property surface (WBS-6.2 #450): `crates/sl-viewer/tests/properties_viewer_tokens.rs` adds 10 proptest properties — every `lab_coat::*` hex is a well-formed `#RRGGBB` (7-char lowercase ASCII hex), is non-empty, is pairwise distinct across the documented 16-constant set, and appears in `TOKENS_CSS`. Every `REQUIRED_CSS_VARS` entry starts with `--`, is non-empty, is unique across the documented set, and appears in `TOKENS_CSS`. `VIEWER_COLOR_SCHEME` declares both `:root` and `:root[data-theme="dark"]` selectors and uses the `color-scheme` property exactly twice.

- Wave-44 plan landed: `WAVE44_SCOPE.md` + `docs/ops/WAVE44_PERT.md` enumerate 6 close-out lanes (3 machine, 3 human-gated) for the 6 unpaid residuals from Wave-43 (396/402 → 402/402 target). Theme: stack-stability closure + i18n migration + eval coverage + supply-chain signing.
- Wave-44 reaudit (Wave-44-D): `audit/SCORECARD.md` refresh at commit `13c974f7` (machine-w44-reaudit); `docs/ops/TRACEABILITY.json` overall_audit wave=Wave-44 commit=13c974f7 (conservative hold at 396/402); `docs/ops/GAP_QA_MATRIX.md` C00 + C08 + PLAN-W8-B rows reflect Wave-44 closure (#368 W44-B6 corpus / #372 W44-B1 loom / #373 PERT correction). 2 of 3 machine lanes shipped 2026-07-24; remaining 6 raw pts across C04 L36 / C08 L76 / C11 L110.

Expand Down
186 changes: 186 additions & 0 deletions crates/sl-viewer/tests/properties_viewer_tokens.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//! Property evidence for sl-viewer's `tokens` module — the design-token
//! single source of truth for the Lab-Coat / viewer color palette.
//!
//! The unit tests in `tokens.rs` pin specific values and exercise
//! `ThemeColors::light() / dark()` against the `lab_coat::*` mirror.
//! These properties pin the broader SSOT invariants:
//!
//! * Every `lab_coat::*` hex constant is a well-formed `#RRGGBB` string
//! (7 chars, leading `#`, then 6 hex digits).
//! * All Lab-Coat hex constants are pairwise distinct — no two share
//! the same value (catches drift where a constant is silently
//! re-aliased to another).
//! * Every Lab-Coat hex appears somewhere in `TOKENS_CSS` so the
//! Rust mirror and the CSS SSOT stay in sync.
//! * Every `REQUIRED_CSS_VARS` entry starts with `--`, has no
//! duplicates, and appears as a substring of `TOKENS_CSS`.
//! * `VIEWER_COLOR_SCHEME` mentions both `:root[data-theme="dark"]`
//! and `:root` so the dark-mode flip is wired.
//!
//! proptest is added to `sl-viewer/[dev-dependencies]` (mirroring the
//! workspace root); see PR #425 for the initial wiring.

use std::collections::HashSet;

use proptest::prelude::*;
use sl_viewer::tokens::{
lab_coat, REQUIRED_CSS_VARS, TOKENS_CSS, VIEWER_COLOR_SCHEME,
};

// ── strategies ──────────────────────────────────────────────────────────────

/// Generate indices into `REQUIRED_CSS_VARS` for prop_any tests.
fn required_var_index_strategy() -> impl Strategy<Value = usize> {
0..REQUIRED_CSS_VARS.len()
}

/// Generate indices into the `lab_coat::*` constants via the documented
/// hex list. We use the indices, then look up the value, so we exercise
/// the actual const definitions (not duplicates).
fn lab_coat_hex_indices_strategy() -> impl Strategy<Value = usize> {
0..lab_coat_hex_list().len()
}

/// The full list of `lab_coat::*` hex constants in stable declaration
/// order. We compute this once via a small reflection-on-source approach:
/// every `pub const` in `lab_coat::*` whose value is a `&'static str`
/// starting with `#`. Since we can't introspect Rust modules at runtime,
/// we hard-code the list (mirroring `tokens.rs`). The constants are
/// public — any new addition requires also extending this list, which
Comment on lines +44 to +49

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The list is manually maintained, and none of the properties compares it with the actual declarations in lab_coat. Adding a new public hex constant without adding it here will leave every test passing, so the claimed exhaustiveness check cannot detect Rust/CSS drift. Tie the test list to a compile-time declaration manifest or add an independently checked expected declaration count. [incomplete implementation]

Severity Level: Major ⚠️
- ⚠️ New Lab-Coat constants can bypass SSOT checks.
- ⚠️ Rust/CSS token drift may reach viewer styling.
- ⚠️ Claimed exhaustiveness depends on manual maintenance.

Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/sl-viewer/tests/properties_viewer_tokens.rs
**Line:** 44:49
**Comment:**
	*Incomplete Implementation: The list is manually maintained, and none of the properties compares it with the actual declarations in `lab_coat`. Adding a new public hex constant without adding it here will leave every test passing, so the claimed exhaustiveness check cannot detect Rust/CSS drift. Tie the test list to a compile-time declaration manifest or add an independently checked expected declaration count.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

/// the `proptest` exhaustiveness check below will catch.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Exhaustiveness claim is inaccurate

The comment claims "the proptest exhaustiveness check below will catch" missing lab_coat constants, but lab_coat_hexes_distinct only verifies that the hardcoded list has no duplicates — it does not check that the list is complete. If a new lab_coat constant is added without updating lab_coat_hex_list(), the test silently won't cover it. Consider adding a compile-time assertion or a test that iterates the actual lab_coat module to verify completeness.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

fn lab_coat_hex_list() -> &'static [&'static str] {
&[
lab_coat::LAB_WHITE,
lab_coat::SLATE,
lab_coat::COBALT,
lab_coat::COBALT_ON_DARK,
lab_coat::ORANGE,
lab_coat::TEAL,
lab_coat::TEAL_ON_DARK,
lab_coat::BG_DARK,
lab_coat::SURFACE_LIGHT,
lab_coat::BORDER_LIGHT,
lab_coat::BORDER_DARK,
lab_coat::TEXT_DARK,
lab_coat::TEXT_MUTED_LIGHT,
lab_coat::TEXT_MUTED_DARK,
lab_coat::DANGER_LIGHT,
lab_coat::DANGER_DARK,
]
}

// ── lab_coat hex well-formedness ────────────────────────────────────────────

proptest! {
/// Property: every `lab_coat::*` hex constant is a 7-char string
/// starting with `#`, followed by 6 lowercase hex digits. Catches
/// drift where someone hand-types an `rgb(…)` literal or a 3-digit
/// hex.
#[test]
fn lab_coat_hex_well_formed(i in lab_coat_hex_indices_strategy()) {
let hex = lab_coat_hex_list()[i];
prop_assert_eq!(hex.len(), 7, "hex {:?} must be 7 chars", hex);
prop_assert!(hex.starts_with('#'), "hex {:?} must start with '#'", hex);
let body = &hex[1..];
prop_assert!(
body.chars().all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()),
"hex {:?} must be lowercase ASCII hex digits only",
hex,
);
}

/// Property: every `lab_coat::*` hex constant is non-empty (sanity
/// check — the well-formedness check above is the stricter version).
#[test]
fn lab_coat_hex_nonempty(i in lab_coat_hex_indices_strategy()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Redundant non-empty check

lab_coat_hex_nonempty verifies that each hex is non-empty, but this is already enforced by lab_coat_hex_well_formed (a 7-character string cannot be empty). The check provides no additional coverage beyond what the well-formedness property already guarantees.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

let hex = lab_coat_hex_list()[i];
prop_assert!(!hex.is_empty(), "lab_coat hex at index {} is empty", i);
}

/// Property: all `lab_coat::*` hex constants are pairwise distinct.
/// No silent re-aliasing.
#[test]
fn lab_coat_hexes_distinct(_i in 0u8..4) {
let list = lab_coat_hex_list();
let set: HashSet<_> = list.iter().collect();
prop_assert_eq!(set.len(), list.len());
}

/// Property: every `lab_coat::*` hex appears as a substring of
/// `TOKENS_CSS` so the Rust mirror and the CSS SSOT stay in sync.
/// If a constant is added without updating the CSS, this fails.
#[test]
fn lab_coat_hex_in_tokens_css(i in lab_coat_hex_indices_strategy()) {
let hex = lab_coat_hex_list()[i];
prop_assert!(
TOKENS_CSS.contains(hex),
"TOKENS_CSS missing lab_coat hex {:?}",
hex,
);
Comment on lines +109 to +119

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: This only checks that each hex occurs somewhere in the stylesheet, not that it is assigned to the corresponding Lab-Coat variable. If an assignment is changed or removed while the same value remains in another declaration or comment, the test still passes and falsely reports the Rust mirror as synchronized. Validate the variable/value assignment together, as the existing token unit test does. [incorrect condition logic]

Severity Level: Major ⚠️
- ⚠️ Incorrect CSS assignments can evade added properties.
- ❌ Viewer colors may fall back after token removal.
- ⚠️ Full Lab-Coat mirror coverage remains incomplete.

Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/sl-viewer/tests/properties_viewer_tokens.rs
**Line:** 109:119
**Comment:**
	*Incorrect Condition Logic: This only checks that each hex occurs somewhere in the stylesheet, not that it is assigned to the corresponding Lab-Coat variable. If an assignment is changed or removed while the same value remains in another declaration or comment, the test still passes and falsely reports the Rust mirror as synchronized. Validate the variable/value assignment together, as the existing token unit test does.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

}
}

// ── REQUIRED_CSS_VARS invariants ────────────────────────────────────────────

proptest! {
/// Property: every `REQUIRED_CSS_VARS` entry starts with `--` (CSS
/// custom property convention).
#[test]
fn required_css_var_starts_with_double_dash(i in required_var_index_strategy()) {
let var = REQUIRED_CSS_VARS[i];
prop_assert!(var.starts_with("--"), "var {:?} must start with '--'", var);
}

/// Property: `REQUIRED_CSS_VARS` has no duplicates.
#[test]
fn required_css_vars_unique(_i in 0u8..4) {
let list = REQUIRED_CSS_VARS;
let set: HashSet<_> = list.iter().collect();
prop_assert_eq!(set.len(), list.len());
}

/// Property: every `REQUIRED_CSS_VARS` entry is non-empty (no
/// empty `--` strings accidentally added).
#[test]
fn required_css_var_nonempty(i in required_var_index_strategy()) {
let var = REQUIRED_CSS_VARS[i];
prop_assert!(!var.is_empty(), "REQUIRED_CSS_VARS[{}] is empty", i);
Comment on lines +129 to +147

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject the bare "--" custom-property name.

required_css_var_starts_with_double_dash accepts "--". required_css_var_nonempty also accepts it because the string is not empty. This does not enforce the documented no-empty-name invariant.

Require at least one character after the prefix. Consider validating the supported CSS identifier format too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sl-viewer/tests/properties_viewer_tokens.rs` around lines 129 - 147,
Update required_css_var_starts_with_double_dash and required_css_var_nonempty to
reject the bare "--" value by requiring each entry to contain at least one
character after the prefix. If supported by the existing test scope, also
validate that the suffix matches the documented CSS custom-property identifier
format.

}

/// Property: every `REQUIRED_CSS_VARS` entry appears as a
/// substring of `TOKENS_CSS`. Catches drift where a var name is
/// added to the list without updating the CSS file.
#[test]
fn required_css_var_in_tokens_css(i in required_var_index_strategy()) {
let var = REQUIRED_CSS_VARS[i];
prop_assert!(
TOKENS_CSS.contains(var),
"TOKENS_CSS missing required CSS var {:?}",
var,
);
Comment on lines +150 to +160

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Substring matching does not establish that a required variable is declared as a CSS custom property. In particular, --sl-text is satisfied by the longer --sl-text-muted name, and --sl-accent is satisfied by --sl-accent-secondary; removing the shorter declaration would therefore leave this test green. Check declaration boundaries, such as a parsed declaration name or a line beginning with the exact variable followed by :. [incorrect condition logic]

Severity Level: Major ⚠️
- ❌ Missing semantic variables can break viewer colors.
- ⚠️ Prefix collisions produce false-positive tests.
- ⚠️ Body and component styles consume affected variables.

Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/sl-viewer/tests/properties_viewer_tokens.rs
**Line:** 150:160
**Comment:**
	*Incorrect Condition Logic: Substring matching does not establish that a required variable is declared as a CSS custom property. In particular, `--sl-text` is satisfied by the longer `--sl-text-muted` name, and `--sl-accent` is satisfied by `--sl-accent-secondary`; removing the shorter declaration would therefore leave this test green. Check declaration boundaries, such as a parsed declaration name or a line beginning with the exact variable followed by `:`.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

}
}

// ── VIEWER_COLOR_SCHEME invariants ──────────────────────────────────────────

proptest! {
/// Property: `VIEWER_COLOR_SCHEME` declares both the default
/// (`:root`) and dark (`:root[data-theme="dark"]`) selectors so the
/// viewer's color-scheme flip is wired.
#[test]
fn viewer_color_scheme_declares_both_selectors(_i in 0u8..4) {
prop_assert!(VIEWER_COLOR_SCHEME.contains(":root"));
prop_assert!(VIEWER_COLOR_SCHEME.contains("[data-theme=\"dark\"]"));
Comment on lines +171 to +173

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The two independent substring assertions do not require the dark selector to be :root[data-theme="dark"]. A stylesheet containing :root plus an unrelated selector containing [data-theme="dark"] would pass even though the root dark-mode rule is missing. Assert the combined selector as one exact required substring or parse the selector. [incorrect condition logic]

Severity Level: Major ⚠️
- ⚠️ Root dark-mode wiring can evade regression tests.
- ❌ Browser color-scheme switching may stop working.
- ⚠️ Viewer theme behavior depends on this selector.

Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/sl-viewer/tests/properties_viewer_tokens.rs
**Line:** 171:173
**Comment:**
	*Incorrect Condition Logic: The two independent substring assertions do not require the dark selector to be `:root[data-theme="dark"]`. A stylesheet containing `:root` plus an unrelated selector containing `[data-theme="dark"]` would pass even though the root dark-mode rule is missing. Assert the combined selector as one exact required substring or parse the selector.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

}

/// Property: `VIEWER_COLOR_SCHEME` declares `color-scheme` for
/// both modes (the W3C CSS prop that triggers browser scrollbar
/// and form-control color flips).
#[test]
fn viewer_color_scheme_declares_color_scheme_property(_i in 0u8..4) {
prop_assert!(VIEWER_COLOR_SCHEME.contains("color-scheme"));
// Both modes must set the property.
let occurrences = VIEWER_COLOR_SCHEME.matches("color-scheme").count();
prop_assert_eq!(occurrences, 2);
Comment on lines +171 to +184

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate complete selector rules instead of independent substrings.

Line 172 succeeds when only :root[data-theme="dark"] exists because that selector already contains :root. Line 173 accepts the dark attribute selector anywhere in the CSS. The color-scheme count can also pass when both declarations occur in one rule or in comments.

Assert a standalone light :root rule, the exact :root[data-theme="dark"] rule, and one color-scheme declaration inside each rule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sl-viewer/tests/properties_viewer_tokens.rs` around lines 171 - 184,
Strengthen viewer_color_scheme_declares_both_selectors and
viewer_color_scheme_declares_color_scheme_property to validate complete CSS
rules rather than substring counts. Assert a standalone light :root rule and the
exact :root[data-theme="dark"] rule, then verify each rule contains exactly one
color-scheme declaration, excluding matches from other rules or comments.

}
}
1 change: 1 addition & 0 deletions docs/ops/TRACEABILITY.json
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@
"crates/sl-viewer/tests/properties_viewer_timeline.rs",
"crates/sl-viewer/tests/properties_viewer_search_memory.rs",
"crates/sl-viewer/tests/properties_viewer_history.rs",
"crates/sl-viewer/tests/properties_viewer_tokens.rs",
"fuzz/fuzz_targets/okf_roundtrip.rs",
"fuzz/fuzz_targets/jsonl_ingest.rs",
".github/workflows/ci.yml",
Expand Down
2 changes: 1 addition & 1 deletion docs/ops/WBS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ without a new audit.
| WBS-4.2 | P4 FTS recall via context-mode and explicit TUI decision | partial | human | `docs/DESIGN.md` §3, §7; `crates/sl-viewer/` | DESIGN P4 residual; C00, C11 |
| WBS-5.1 | P5 deterministic dedup merge and crash/lost-work recovery E2E | done | machine | `src/domain/merge.rs`; `src/domain/worklog.rs`; `tests/merge_recovery.rs` | FR-011; T-024, T-035; C03 |
| WBS-6.1 | P6 85% coverage gate and deterministic golden corpus | done | machine | `.github/workflows/ci.yml`; `tests/okf_golden.rs`; `tests/fixtures/okf/` | T-037, T-038; C01, C08 |
| WBS-6.2 | P6 property tests, fuzzing, race checks, and enforced performance budgets | partial | machine | `tests/properties.rs`; `crates/sl-viewer/tests/properties_viewer.rs`; `crates/sl-viewer/tests/properties_viewer_theme_url.rs`; `crates/sl-viewer/tests/properties_viewer_unfinished_tab.rs`; `crates/sl-viewer/tests/properties_viewer_timeline.rs`; `crates/sl-viewer/tests/properties_viewer_search_memory.rs`; `crates/sl-viewer/tests/properties_viewer_history.rs`; `fuzz/fuzz_targets/okf_roundtrip.rs`; `fuzz/fuzz_targets/jsonl_ingest.rs`; `.github/workflows/ci.yml`; `.github/workflows/bench-gate.yml`; `docs/ops/perf-baseline.json`; `scripts/bench-gate.ps1`; `benches/pipeline.rs`; `tests/loom_model.rs` | DESIGN P6 residual; C00 L6-L8; C07 L66-L68; C08 L74; perf-budget enforced Wave-26 #223; p95 latency enforced Wave-30 #256; FSM properties Wave-31 #261; soft loom Wave-31 #264; viewer corpus_paths/parquet/settings properties #425; viewer theme + daemon_url properties #427; viewer unfinished_tab properties + fuzz/rootless CI drift fixes #428; viewer bundle_diff + timeline properties + web_exports/hmetic-pin cleanups #432; viewer bundle_diff properties #434; viewer search/memory properties #435; viewer history_tab properties #444; full loom/shuttle unpaid |
| WBS-6.2 | P6 property tests, fuzzing, race checks, and enforced performance budgets | partial | machine | `tests/properties.rs`; `crates/sl-viewer/tests/properties_viewer.rs`; `crates/sl-viewer/tests/properties_viewer_theme_url.rs`; `crates/sl-viewer/tests/properties_viewer_unfinished_tab.rs`; `crates/sl-viewer/tests/properties_viewer_timeline.rs`; `crates/sl-viewer/tests/properties_viewer_search_memory.rs`; `crates/sl-viewer/tests/properties_viewer_history.rs`; `crates/sl-viewer/tests/properties_viewer_tokens.rs`; `fuzz/fuzz_targets/okf_roundtrip.rs`; `fuzz/fuzz_targets/jsonl_ingest.rs`; `.github/workflows/ci.yml`; `.github/workflows/bench-gate.yml`; `docs/ops/perf-baseline.json`; `scripts/bench-gate.ps1`; `benches/pipeline.rs`; `tests/loom_model.rs` | DESIGN P6 residual; C00 L6-L8; C07 L66-L68; C08 L74; perf-budget enforced Wave-26 #223; p95 latency enforced Wave-30 #256; FSM properties Wave-31 #261; soft loom Wave-31 #264; viewer corpus_paths/parquet/settings properties #425; viewer theme + daemon_url properties #427; viewer unfinished_tab properties + fuzz/rootless CI drift fixes #428; viewer bundle_diff + timeline properties + web_exports/hmetic-pin cleanups #432; viewer bundle_diff properties #434; viewer search/memory properties #435; viewer history_tab properties #444; viewer tokens SSOT properties #450; full loom/shuttle unpaid |

## audit-v38 waves

Expand Down
Loading