Repository: preactjs/preact-devtools
Pull request #407 (2022-08-23 19:05:54)

Prepare code for new state management


Patch:
diff --git a/src/adapter/protocol/events.ts b/src/adapter/protocol/events.ts
index 79d12af..8a040b3 100644
--- a/src/adapter/protocol/events.ts
+++ b/src/adapter/protocol/events.ts
@@ -126,14 +126,14 @@ export function applyOperationsV2(store: Store, data: number[]) {
 		tree,
 		reasons,
 		stats,
-	} = ops2Tree(store.nodes.$, store.roots.$, data);
+	} = ops2Tree(store.nodes.value, store.roots.value, data);
 
 	// Update store data
-	store.roots.$ = roots;
-	store.nodes.$ = tree;
+	store.roots.value = roots;
+	store.nodes.value = tree;
 
-	if (store.inspectData.$) {
-		const id = store.inspectData.$.id;
+	if (store.inspectData.value) {
+		const id = store.inspectData.value.id;
 		if (tree.has(id)) {
 			store.notify("inspect", id);
 		}
@@ -141,16 +141,21 @@ export function applyOperationsV2(store: Store, data: number[]) {
 
 	// If we are profiling, we'll make a frozen copy of the mutable
 	// elements tree because the profiler can step through time
-	if (store.profiler.isRecording.$) {
-		recordProfilerCommit(store.nodes.$, store.profiler, rendered, commitRootId);
+	if (store.profiler.isRecording.value) {
+		recordProfilerCommit(
+			store.nodes.value,
+			store.profiler,
+			rendered,
+			commitRootId,
+		);
 		store.profiler.renderReasons.update(m => {
 			m.set(commitRootId, reasons);
 		});
 	}
 
 	if (stats !== null) {
-		if (store.stats.data.$ === null) {
-			store.stats.data.$ = stats;
+		if (store.stats.data.value === null) {
+			store.stats.data.value = stats;
 		} else {
 			store.stats.data.update(v => {
 				if (v === null) return;
@@ -193,18 +198,18 @@ export function applyOperationsV2(store: Store, data: number[]) {
 export function applyEvent(store: Store, type: string, data: any) {
 	switch (type) {
 		case "attach":
-			if (!store.profiler.isSupported.$) {
-				store.profiler.isSupported.$ = !!data.supportsProfiling;
+			if (!store.profiler.isSupported.value) {
+				store.profiler.isSupported.value = !!data.supportsProfiling;
 			}
-			if (!store.profiler.supportsRenderReasons.$) {
-				store.profiler.supportsRenderReasons.$ = !!data.supportsRenderReasons;
+			if (!store.profiler.supportsRenderReasons.value) {
+				store.profiler.supportsRenderReasons.value = !!data.supportsRenderReasons;
 			}
 
-			if (!store.supports.hooks.$) {
-				store.supports.hooks.$ = !!data.supportsHooks;
+			if (!store.supports.hooks.value) {
+				store.supports.hooks.value = !!data.supportsHooks;
 			}
 
-			if (store.profiler.highlightUpdates.$) {
+			if (store.profiler.highlightUpdates.value) {
 				store.emit("start-highlight-updates", null);
 			}
 			break;
@@ -216,15 +221,15 @@ export function applyEvent(store: Store, type: string, data: any) {
 			break;
 		case "inspect-result": {
 			const { props, state, context } = store.sidebar;
-			store.inspectData.$ = data;
+			store.inspectData.value = data;
 
-			if (store.selection.selected.$ !== data.id) {
+			if (store.selection.selected.value !== data.id) {
 				store.selection.selectById(data.id);
 
 				// Reset collapsible state
-				props.uncollapsed.$ = [];
-				state.uncollapsed.$ = [];
-				context.uncollapsed.$ = [];
+				props.uncollapsed.value = [];
+				state.uncollapsed.value = [];
+				context.uncollapsed.value = [];
 			}
 			break;
 		}
@@ -232,7 +237,7 @@ export function applyEvent(store: Store, type: string, data: any) {
 			store.selection.selectById(data);
 			break;
 		case "stop-picker":
-			store.isPicking.$ = false;
+			store.isPicking.value = false;
 			break;
 	}
 }
diff --git a/src/adapter/protocol/legacy/operationsV1.ts b/src/adapter/protocol/legacy/operationsV1.ts
index 718cf4f..cf2508b 100644
--- a/src/adapter/protocol/legacy/operationsV1.ts
+++ b/src/adapter/protocol/legacy/operationsV1.ts
@@ -4,18 +4,19 @@ import { Store } from "../../../view/store/types";
 import { deepClone } from "../../shared/utils";
 
 export function applyOperationsV1(store: Store, data: number[]) {
-	const nodes = new Map(store.nodes.$);
+	const nodes = new Map(store.nodes.value);
 
 	let i = data[1] + 1;
 	const strings = parseTable(data.slice(1, i + 1));
 
-	const inspected = store.inspectData.$ != null ? store.inspectData.$.id : -2;
+	const inspected =
+		store.inspectData.value != null ? store.inspectData.value.id : -2;
 
 	for (; i < data.length; i++) {
 		switch (data[i]) {
 			case MsgTypes.ADD_ROOT: {
 				const id = data[i + 1];
-				store.roots.$.push(id);
+				store.roots.value.push(id);
 				i += 1;
 				break;
 			}
@@ -130,5 +131,5 @@ export function applyOperationsV1(store: Store, data: number[]) {
 	}
 
 	store.roots.update();
-	store.nodes.$ = nodes;
+	store.nodes.value = nodes;
 }
diff --git a/src/shells/shared/panel/settings.ts b/src/shells/shared/panel/settings.ts
index 0f3bf86..8e1bc5d 100644
--- a/src/shells/shared/panel/settings.ts
+++ b/src/shells/shared/panel/settings.ts
@@ -25,11 +25,11 @@ export async function loadSettings(window: Window, store: Store) {
 
 		if (settings) {
 			if (["light", "dark", "auto"].includes(settings.theme)) {
-				store.theme.$ = settings.theme;
+				store.theme.value = settings.theme;
 			}
-			store.profiler.captureRenderReasons.$ = !!settings.captureRenderReasons;
-			store.profiler.highlightUpdates.$ = !!settings.highlightUpdates;
-			store.debugMode.$ = !!settings.debugMode;
+			store.profiler.captureRenderReasons.value = !!settings.captureRenderReasons;
+			store.profiler.highlightUpdates.value = !!settings.highlightUpdates;
+			store.debugMode.value = !!settings.debugMode;
 			if (settings.componentFilters) {
 				store.filter.restore(settings.componentFilters);
 			}
diff --git a/src/view/components/Devtools.tsx b/src/view/components/Devtools.tsx
index 8a5c3c2..a358528 100644
--- a/src/view/components/Devtools.tsx
+++ b/src/view/components/Devtools.tsx
@@ -16,7 +16,7 @@ import { Settings } from "./settings/Settings";
 import { StatsPanel } from "./stats/StatsPanel";
 
 export function DevTools(props: { store: Store; window: Window }) {
-	const panel = useObserver(() => props.store.activePanel.$);
+	const panel = useObserver(() => props.store.activePanel.value);
 
 	const showElements = panel === Panel.ELEMENTS;
 	const showProfiler = panel === Panel.PROFILER;
@@ -33,7 +33,9 @@ export function DevTools(props: { store: Store; window: Window }) {
 							<SmallTabGroup class={s.switcher}>
 								<div class={s.switcherInner}>
 									<SmallTab
-										onClick={() => (props.store.activePanel.$ = Panel.ELEMENTS)}
+										onClick={() =>
+											(props.store.activePanel.value = Panel.ELEMENTS)
+										}
 										checked={showElements}
 										name="root-panel"
 										value={Panel.ELEMENTS}
@@ -41,7 +43,9 @@ export function DevTools(props: { store: Store; window: Window }) {
 										Elements
 									</SmallTab>
 									<SmallTab
-										onClick={() => (props.store.activePanel.$ = Panel.PROFILER)}
+										onClick={() =>
+											(props.store.activePanel.value = Panel.PROFILER)
+										}
 										checked={showProfiler}
 										name="root-panel"
 										value={Panel.PROFILER}
@@ -50,7 +54,7 @@ export function DevTools(props: { store: Store; window: Window }) {
 									</SmallTab>
 									<SmallTab
 										onClick={() =>
-											(props.store.activePanel.$ = Panel.STATISTICS)
+											(props.store.activePanel.value = Panel.STATISTICS)
 										}
 										checked={showStats}
 										name="root-panel"
@@ -59,7 +63,9 @@ export function DevTools(props: { store: Store; window: Window }) {
 										Statistics
 									</SmallTab>
 									<SmallTab
-										onClick={() => (props.store.activePanel.$ = Panel.SETTINGS)}
+										onClick={() =>
+											(props.store.activePanel.value = Panel.SETTINGS)
+										}
 										checked={showSettings}
 										name="root-panel"
 										value={Panel.SETTINGS}
diff --git a/src/view/components/ThemeSwitcher.tsx b/src/view/components/ThemeSwitcher.tsx
index 1e461cd..b531c59 100644
--- a/src/view/components/ThemeSwitcher.tsx
+++ b/src/view/components/ThemeSwitcher.tsx
@@ -4,7 +4,7 @@ import { useObserver, useStore } from "../store/react-bindings";
 
 export function ThemeSwitcher() {
 	const store = useStore();
-	let theme = useObserver(() => store.theme.$);
+	let theme = useObserver(() => store.theme.value);
 	if (theme === "auto") {
 		theme = matchMedia("(prefers-color-scheme: dark)").matches
 			? "dark"
diff --git a/src/view/components/elements/OwnerInfo.tsx b/src/view/components/elements/OwnerInfo.tsx
index 225e9fb..de24e45 100644
--- a/src/view/components/elements/OwnerInfo.tsx
+++ b/src/view/components/elements/OwnerInfo.tsx
@@ -6,12 +6,12 @@ import { SidebarPanel } from "../sidebar/SidebarPanel";
 export function OwnerInfo() {
 	const store = useStore();
 
-	const selectedId = useObserver(() => store.selection.selected.$);
+	const selectedId = useObserver(() => store.selection.selected.value);
 	const data = useObserver(() => {
 		const owners: DevNode[] = [];
-		const selectedId = store.selection.selected.$;
+		const selectedId = store.selection.selected.value;
 
-		const nodes = store.nodes.$;
+		const nodes = store.nodes.value;
 
 		let id = selectedId;
 		let current: DevNode | undefined;
diff --git a/src/view/components/elements/TreeBar.tsx b/src/view/components/elements/TreeBar.tsx
index db9de4b..6128474 100644
--- a/src/view/components/elements/TreeBar.tsx
+++ b/src/view/components/elements/TreeBar.tsx
@@ -10,7 +10,7 @@ import { OutsideClick } from "../OutsideClick";
 
 export function TreeBar() {
 	const store = useStore();
-	const isPicking = useObserver(() => store.isPicking.$);
+	const isPicking = useObserver(() => store.isPicking.value);
 	const { value, count, selected, goPrev, goNext } = useSearch();
 
 	const [filterVisible, setFilterVisible] = useState(false);
@@ -36,7 +36,7 @@ export function TreeBar() {
 					title="Pick a Component from the page"
 					testId="inspect-btn"
 					onClick={() => {
-						store.isPicking.$ = !isPicking;
+						store.isPicking.value = !isPicking;
 						store.notify(!isPicking ? "start-picker" : "stop-picker", null);
 					}}
 				>
@@ -135,13 +135,13 @@ function FilterCheck({
 
 export function FilterPopup() {
 	const store = useStore();
-	const [filterDom, setFilterDom] = useState(store.filter.filterDom.$);
+	const [filterDom, setFilterDom] = useState(store.filter.filterDom.value);
 	const [filterFragment, setFilterFragment] = useState(
-		store.filter.filterFragment.$,
+		store.filter.filterFragment.value,
 	);
-	const [filterHoc, setFilterHoc] = useState(store.filter.filterHoc.$);
-	const [filterRoot, setFilterRoot] = useState(store.filter.filterRoot.$);
-	const [filters, setFilters] = useState(store.filter.filters.$);
+	const [filterHoc, setFilterHoc] = useState(store.filter.filterHoc.value);
+	const [filterRoot, setFilterRoot] = useState(store.filter.filterRoot.value);
+	const [filters, setFilters] = useState(store.filter.filters.value);
 
 	return (
 		<div class={s.filter} data-testid="filter-popup">
@@ -149,12 +149,12 @@ export function FilterPopup() {
 				onSubmit={e => {
 					e.preventDefault();
 
-					store.filter.filterDom.$ = filterDom;
-					store.filter.filterFragment.$ = filterFragment;
-					store.filter.filterRoot.$ = filterRoot;
-					store.filter.filterHoc.$ = filterHoc;
+					store.filter.filterDom.value = filterDom;
+					store.filter.filterFragment.value = filterFragment;
+					store.filter.filterRoot.value = filterRoot;
+					store.filter.filterHoc.value = filterHoc;
 
-					store.filter.filters.$ = filters;
+					store.filter.filters.value = filters;
 
 					store.filter.submit();
 				}}
diff --git a/src/view/components/elements/TreeView.tsx b/src/view/components/elements/TreeView.tsx
index 00db66b..369432c 100644
--- a/src/view/components/elements/TreeView.tsx
+++ b/src/view/components/elements/TreeView.tsx
@@ -24,8 +24,8 @@ const highlightNode = debounce(
 
 export function TreeView() {
 	const store = useStore();
-	const nodeList = useObserver(() => store.nodeList.$);
-	const roots = useObserver(() => store.roots.$);
+	const nodeList = useObserver(() => store.nodeList.value);
+	const roots = useObserver(() => store.roots.value);
 	const { collapseNode, collapsed } = useCollapser();
 	const { selected, selectNext, selectPrev } = useSelection();
 
@@ -33,19 +33,19 @@ export function TreeView() {
 		selected,
 		onCollapse: collapseNode,
 		canCollapse: id => {
-			const node = store.nodes.$.get(id);
+			const node = store.nodes.value.get(id);
 			return node ? node.children.length > 0 : false;
 		},
 		checkCollapsed: id => collapsed.has(id),
 		onNext: () => {
 			selectNext();
-			const s = store.selection.selected.$;
+			const s = store.selection.selected.value;
 			highlightNode(store.notify, s);
 			store.notify("inspect", s);
 		},
 		onPrev: () => {
 			selectPrev();
-			const s = store.selection.selected.$;
+			const s = store.selection.selected.value;
 			highlightNode(store.notify, s);
 			store.notify("inspect", s);
 		},
@@ -175,10 +175,10 @@ export function TreeItem(props: { key: any; id: ID; top: number }) {
 	const store = useStore();
 	const as = useSelection();
 	const { collapsed, toggle } = useCollapser();
-	const node = useObserver(() => store.nodes.$.get(id) || null);
-	const filterRoot = useObserver(() => store.filter.filterRoot.$);
-	const filterHoc = useObserver(() => store.filter.filterHoc.$);
-	const roots = useObserver(() => store.roots.$);
+	const node = useObserver(() => store.nodes.value.get(id) || null);
+	const filterRoot = useObserver(() => store.filter.filterRoot.value);
+	const filterHoc = useObserver(() => store.filter.filterHoc.value);
+	const roots = useObserver(() => store.roots.value);
 	const onToggle = () => toggle(id);
 	const ref = useRef<HTMLDivElement>(null);
 
@@ -279,13 +279,13 @@ export function Arrow() {
 
 export function HighlightPane(props: { treeDom: HTMLDivElement | null }) {
 	const store = useStore();
-	const nodes = useObserver(() => store.nodes.$);
+	const nodes = useObserver(() => store.nodes.value);
 	const { selected } = useSelection();
 	const { collapsed } = useCollapser();
 
 	// Subscribe to nodeList so that we rerender whenever nodes
 	// are collapsed
-	const list = useObserver(() => store.nodeList.$);
+	const list = useObserver(() => store.nodeList.value);
 
 	const [pos, setPos] = useState({ top: 0, height: 0 });
 	useEffect(() => {
diff --git a/src/view/components/profiler/components/CommitInfo/CommitInfo.tsx b/src/view/components/profiler/components/CommitInfo/CommitInfo.tsx
index 16c8102..f891b26 100644
--- a/src/view/components/profiler/components/CommitInfo/CommitInfo.tsx
+++ b/src/view/components/profiler/components/CommitInfo/CommitInfo.tsx
@@ -5,8 +5,8 @@ import { useStore, useObserver } from "../../../../store/react-bindings";
 
 export function CommitInfo() {
 	const store = useStore();
-	const commit = useObserver(() => store.profiler.activeCommit.$);
-	const isRecording = useObserver(() => store.profiler.isRecording.$);
+	const commit = useObserver(() => store.profiler.activeCommit.value);
+	const isRecording = useObserver(() => store.profiler.isRecording.value);
 
 	if (commit === null || isRecording) {
 		return null;
diff --git a/src/view/components/profiler/components/CommitInfo/DebugInfo.tsx b/src/view/components/profiler/components/CommitInfo/DebugInfo.tsx
index 2681f90..a8256ae 100644
--- a/src/view/components/profiler/components/CommitInfo/DebugInfo.tsx
+++ b/src/view/components/profiler/components/CommitInfo/DebugInfo.tsx
@@ -11,12 +11,12 @@ const TimeRange = ({ from, to }: { from: number; to: number }) => (
 
 export function DebugProfilerInfo() {
 	const store = useStore();
-	const commit = useObserver(() => store.profiler.activeCommit.$);
-	const selected = useObserver(() => store.profiler.selectedNode.$);
-	const isRecording = useObserver(() => store.profiler.isRecording.$);
+	const commit = useObserver(() => store.profiler.activeCommit.value);
+	const selected = useObserver(() => store.profiler.selectedNode.value);
+	const isRecording = useObserver(() => store.profiler.isRecording.value);
 	const pos = useObserver(() => {
-		const s = store.profiler.selectedNodeId.$;
-		return store.profiler.flamegraphNodes.$.get(s);
+		const s = store.profiler.selectedNodeId.value;
+		return store.profiler.flamegraphNodes.value.get(s);
 	})!;
 
 	if (commit === null || isRecording || !selected || !pos) {
diff --git a/src/view/components/profiler/components/Profiler.tsx b/src/view/components/profiler/components/Profiler.tsx
index 88c2cc0..8c7cdf2 100644
--- a/src/view/components/profiler/components/Profiler.tsx
+++ b/src/view/components/profiler/components/Profiler.tsx
@@ -16,8 +16,8 @@ import { Message, MessageBtn } from "../../Message/Message";
 
 export function Profiler() {
 	const store = useStore();
-	const showDebug = useObserver(() => store.debugMode.$);
-	const statsRecording = useObserver(() => store.stats.isRecording.$);
+	const showDebug = useObserver(() => store.debugMode.value);
+	const statsRecording = useObserver(() => store.stats.isRecording.value);
 
 	return (
 		<SidebarLayout>
@@ -39,7 +39,7 @@ export function Profiler() {
 							Stats recording is enabled. Timings may be less accurate.
 							<MessageBtn
 								onClick={() => {
-									store.stats.isRecording.$ = false;
+									store.stats.isRecording.value = false;
 								}}
 								testId="disable-stats-recording"
 							>
diff --git a/src/view/components/profiler/components/ProfilerInfo/ProfilerInfo.tsx b/src/view/components/profiler/components/ProfilerInfo/ProfilerInfo.tsx
index cbd1fa9..2a34936 100644
--- a/src/view/components/profiler/components/ProfilerInfo/ProfilerInfo.tsx
+++ b/src/view/components/profiler/components/ProfilerInfo/ProfilerInfo.tsx
@@ -4,9 +4,9 @@ import { RecordBtn } from "../TimelineBar/TimelineBar";
 
 export function ProfilerInfo() {
 	const store = useStore();
-	const isRecording = useObserver(() => store.profiler.isRecording.$);
-	const isSupported = useObserver(() => store.profiler.isSupported.$);
-	const commits = useObserver(() => store.profiler.commits.$);
+	const isRecording = useObserver(() => store.profiler.isRecording.value);
+	const isSupported = useObserver(() => store.profiler.isSupported.value);
+	const commits = useObserver(() => store.profiler.commits.value);
 
 	if (!isSupported) {
 		return (
diff --git a/src/view/components/profiler/components/RenderReasons.tsx b/src/view/components/profiler/components/RenderReasons.tsx
index 6637439..ae11071 100644
--- a/src/view/components/profiler/components/RenderReasons.tsx
+++ b/src/view/components/profiler/components/RenderReasons.tsx
@@ -25,14 +25,16 @@ function getReasonName(reason: RenderReason) {
 
 export function RenderReasons() {
 	const store = useStore();
-	const isRecording = useObserver(() => store.profiler.isRecording.$);
-	const commits = useObserver(() => store.profiler.commits.$);
-	const reason = useObserver(() => store.profiler.activeReason.$);
-	const commit = useObserver(() => store.profiler.activeCommit.$);
-	const selected = useObserver(() => store.profiler.selectedNode.$);
-	const isSupported = useObserver(() => store.profiler.supportsRenderReasons.$);
+	const isRecording = useObserver(() => store.profiler.isRecording.value);
+	const commits = useObserver(() => store.profiler.commits.value);
+	const reason = useObserver(() => store.profiler.activeReason.value);
+	const commit = useObserver(() => store.profiler.activeCommit.value);
+	const selected = useObserver(() => store.profiler.selectedNode.value);
+	const isSupported = useObserver(
+		() => store.profiler.supportsRenderReasons.value,
+	);
 	const captureReason = useObserver(
-		() => store.profiler.captureRenderReasons.$,
+		() => store.profiler.captureRenderReasons.value,
 	);
 
 	if (commits.length === 0 || isRecording) {
@@ -71,7 +73,7 @@ export function RenderReasons() {
 							onClick={() => {
 								const value = !captureReason;
 								store.profiler.setRenderReasonCapture(value);
-								store.profiler.isRecording.$ = true;
+								store.profiler.isRecording.value = true;
 								store.emit("start-profiling", {
 									captureRenderReasons: value,
 								});
diff --git a/src/view/components/profiler/components/RenderedAt/DebugNodeNav.tsx b/src/view/components/profiler/components/RenderedAt/DebugNodeNav.tsx
index 9219c90..dfb8f9d 100644
--- a/src/view/components/profiler/components/RenderedAt/DebugNodeNav.tsx
+++ b/src/view/components/profiler/components/RenderedAt/DebugNodeNav.tsx
@@ -5,10 +5,10 @@ import { DevNode } from "../../../../store/types";
 
 export function DebugNodeNav() {
 	const store = useStore();
-	const selected = useObserver(() => store.profiler.selectedNodeId.$);
-	const commit = useObserver(() => store.profiler.activeCommit.$);
+	const selected = useObserver(() => store.profiler.selectedNodeId.value);
+	const commit = useObserver(() => store.profiler.activeCommit.value);
 	const nodes = useObserver(() => {
-		const commit = store.profiler.activeCommit.$;
+		const commit = store.profiler.activeCommit.value;
 		if (!commit) return [];
 
 		const out: DevNode[] = [];
@@ -26,7 +26,7 @@ export function DebugNodeNav() {
 
 		return out;
 	});
-	const isRecording = useObserver(() => store.profiler.isRecording.$);
+	const isRecording = useObserver(() => store.profiler.isRecording.value);
 
 	if (isRecording) {
 		return null;
@@ -44,7 +44,7 @@ export function DebugNodeNav() {
 								key={node.id}
 								class="rendered-at-item"
 								data-active={selected === node.id}
-								onClick={() => (store.profiler.selectedNodeId.$ = node.id)}
+								onClick={() => (store.profiler.selectedNodeId.value = node.id)}
 							>
 								<span style="display: flex; justify-content: space-between; width: 100%">
 									<span>
diff --git a/src/view/components/profiler/components/RenderedAt/RenderedAt.tsx b/src/view/components/profiler/components/RenderedAt/RenderedAt.tsx
index df972dc..4fb62d3 100644
--- a/src/view/components/profiler/components/RenderedAt/RenderedAt.tsx
+++ b/src/view/components/profiler/components/RenderedAt/RenderedAt.tsx
@@ -5,12 +5,12 @@ import { formatTime } from "../../util";
 
 export function RenderedAt() {
 	const store = useStore();
-	const commit = useObserver(() => store.profiler.activeCommit.$);
-	const selected = useObserver(() => store.profiler.selectedNodeId.$);
+	const commit = useObserver(() => store.profiler.activeCommit.value);
+	const selected = useObserver(() => store.profiler.selectedNodeId.value);
 	const data = useObserver(() => {
-		const id = store.profiler.selectedNodeId.$;
+		const id = store.profiler.selectedNodeId.value;
 
-		return store.profiler.commits.$.reduce<
+		return store.profiler.commits.value.reduce<
 			Array<{ index: number; selfDuration: number }>
 		>((acc, commit, i) => {
 			if (!commit.rendered.has(id)) return acc;
@@ -24,7 +24,7 @@ export function RenderedAt() {
 		}, []);
 	});
 
-	const commitIdx = useObserver(() => store.profiler.activeCommitIdx.$);
+	const commitIdx = useObserver(() => store.profiler.activeCommitIdx.value);
 
 	if (commit === null) return null;
 
@@ -38,7 +38,7 @@ export function RenderedAt() {
 						class="rendered-at-item"
 						data-active={selected === commit.commitRootId}
 						onClick={() =>
-							(store.profiler.selectedNodeId.$ = commit.commitRootId)
+							(store.profiler.selectedNodeId.value = commit.commitRootId)
 						}
 					>
 						<span>{commitRoot.name}</span>
@@ -59,7 +59,7 @@ export function RenderedAt() {
 										class="rendered-at-item"
 										data-active={commitIdx === node.index}
 										onClick={() =>
-											(store.profiler.activeCommitIdx.$ = node.index)
+											(store.profiler.activeCommitIdx.value = node.index)
 										}
 									>
 										<span>
diff --git a/src/view/components/profiler/components/SidebarHeader.tsx b/src/view/components/profiler/components/SidebarHeader.tsx
index 93a45c1..f32c3a1 100644
--- a/src/view/components/profiler/components/SidebarHeader.tsx
+++ b/src/view/components/profiler/components/SidebarHeader.tsx
@@ -13,7 +13,7 @@ import { Icon } from "../../icons";
 
 export function SidebarHeader() {
 	const store = useStore();
-	const selected = useObserver(() => store.profiler.selectedNode.$);
+	const selected = useObserver(() => store.profiler.selectedNode.value);
 	const emit = useEmitter();
 	const log = useCallback(() => {
 		if (selected) emit("log", { id: selected.id, children: selected.children });
diff --git a/src/view/components/profiler/components/TimelineBar/TimelineBar.tsx b/src/view/components/profiler/components/TimelineBar/TimelineBar.tsx
index 27ee9a9..5d3563f 100644
--- a/src/view/components/profiler/components/TimelineBar/TimelineBar.tsx
+++ b/src/view/components/profiler/components/TimelineBar/TimelineBar.tsx
@@ -11,16 +11,18 @@ import { Icon } from "../../../icons";
 
 export function TimelineBar() {
 	const store = useStore();
-	const commits = useObserver(() => store.profiler.commits.$);
-	const isRecording = useObserver(() => store.profiler.isRecording.$);
-	const isSupported = useObserver(() => store.profiler.isSupported.$);
-	const selectedCommit = useObserver(() => store.profiler.activeCommitIdx.$);
+	const commits = useObserver(() => store.profiler.commits.value);
+	const isRecording = useObserver(() => store.profiler.isRecording.value);
+	const isSupported = useObserver(() => store.profiler.isSupported.value);
+	const selectedCommit = useObserver(
+		() => store.profiler.activeCommitIdx.value,
+	);
 	const stats = useObserver(() => {
 		return {
-			max: Math.max(16, ...store.profiler.commits.$.map(x => x.duration)),
+			max: Math.max(16, ...store.profiler.commits.value.map(x => x.duration)),
 			min: Math.max(
 				0,
-				Math.min(...store.profiler.commits.$.map(x => x.duration)),
+				Math.min(...store.profiler.commits.value.map(x => x.duration)),
 			),
 		};
 	});
@@ -34,19 +36,22 @@ export function TimelineBar() {
 				flamegraphType,
 			} = store.profiler;
 
-			activeCommitIdx.$ = n;
-			const commit = activeCommit.$;
+			activeCommitIdx.value = n;
+			const commit = activeCommit.value;
 			if (!commit) return;
 
-			selectedNodeId.$ = getCommitInitalSelectNodeId(commit, flamegraphType.$);
+			selectedNodeId.value = getCommitInitalSelectNodeId(
+				commit,
+				flamegraphType.value,
+			);
 		},
 		[store],
 	);
 
 	const onReloadAndProfile = useCallback(() => {
-		store.profiler.isRecording.$ = true;
+		store.profiler.isRecording.value = true;
 		store.emit("reload-and-profile", {
-			captureRenderReasons: store.profiler.captureRenderReasons.$,
+			captureRenderReasons: store.profiler.captureRenderReasons.value,
 		});
 	}, []);
 
@@ -100,17 +105,17 @@ export function TimelineBar() {
 
 export function RecordBtn() {
 	const store = useStore();
-	const isRecording = useObserver(() => store.profiler.isRecording.$);
-	const isSupported = useObserver(() => store.profiler.isSupported.$);
+	const isRecording = useObserver(() => store.profiler.isRecording.value);
+	const isSupported = useObserver(() => store.profiler.isSupported.value);
 
 	const onClick = useCallback(() => {
 		const { isRecording, captureRenderReasons } = store.profiler;
-		const v = !isRecording.$;
-		isRecording.$ = v;
+		const v = !isRecording.value;
+		isRecording.value = v;
 
 		if (v) {
 			store.emit("start-profiling", {
-				captureRenderReasons: captureRenderReasons.$,
+				captureRenderReasons: captureRenderReasons.value,
 			});
 		} else {
 			store.emit("stop-profiling", null);
diff --git a/src/view/components/profiler/data/commits.ts b/src/view/components/profiler/data/commits.ts
index bb0a8b1..3eeffdf 100644
--- a/src/view/components/profiler/data/commits.ts
+++ b/src/view/components/profiler/data/commits.ts
@@ -1,5 +1,5 @@
 import { ID, DevNode } from "../../../store/types";
-import { Observable, valoo, watch } from "../../../valoo";
+import { Signal, signal, watch } from "../../../valoo";
 import { getRoot } from "../flamegraph/FlamegraphStore";
 import {
 	RenderReasonMap,
@@ -35,40 +35,40 @@ export interface ProfilerState {
 	/**
 	 * Flag to indicate if profiling is supported by the attached renderer.
 	 */
-	isSupported: Observable<boolean>;
+	isSupported: Signal<boolean>;
 
 	// Render reasons
-	supportsRenderReasons: Observable<boolean>;
-	captureRenderReasons: Observable<boolean>;
+	supportsRenderReasons: Signal<boolean>;
+	captureRenderReasons: Signal<boolean>;
 	setRenderReasonCapture: (v: boolean) => void;
 
 	// Highlight updates
-	highlightUpdates: Observable<boolean>;
+	highlightUpdates: Signal<boolean>;
 
 	/**
 	 * Flag that indicates if we are currently
 	 * recording commits to be displayed in the
 	 * profiler.
 	 */
-	isRecording: Observable<boolean>;
-	commits: Observable<CommitData[]>;
+	isRecording: Signal<boolean>;
+	commits: Signal<CommitData[]>;
 
 	// Selection
-	activeCommitIdx: Observable<number>;
-	activeCommit: Observable<CommitData | null>;
-	selectedNodeId: Observable<ID>;
-	selectedNode: Observable<DevNode | null>;
+	activeCommitIdx: Signal<number>;
+	activeCommit: Signal<CommitData | null>;
+	selectedNodeId: Signal<ID>;
+	selectedNode: Signal<DevNode | null>;
 
 	// Render reasons
-	renderReasons: Observable<Map<ID, RenderReasonMap>>;
-	activeReason: Observable<RenderReasonData | null>;
+	renderReasons: Signal<Map<ID, RenderReasonMap>>;
+	activeReason: Signal<RenderReasonData | null>;
 
 	// View state
-	flamegraphType: Observable<FlamegraphType>;
+	flamegraphType: Signal<FlamegraphType>;
 
 	// Flamegraph mode
-	flamegraphNodes: Observable<Map<ID, NodeTransform>>;
-	rankedNodes: Observable<NodeTransform[]>;
+	flamegraphNodes: Signal<Map<ID, NodeTransform>>;
+	rankedNodes: Signal<NodeTransform[]>;
 }
 
 function getMaxSelfDurationNode(commit: CommitData) {
@@ -100,56 +100,58 @@ export function getCommitInitalSelectNodeId(
  * any methods, to not go down the OOP rabbit hole.
  */
 export function createProfiler(): ProfilerState {
-	const commits = valoo<CommitData[]>([]);
-	const isSupported = valoo(false);
+	const commits = signal<CommitData[]>([]);
+	const isSupported = signal(false);
 
 	// Render Reasons
-	const supportsRenderReasons = valoo(false);
-	const renderReasons = valoo<Map<ID, RenderReasonMap>>(new Map());
-	const captureRenderReasons = valoo(false);
+	const supportsRenderReasons = signal(false);
+	const renderReasons = signal<Map<ID, RenderReasonMap>>(new Map());
+	const captureRenderReasons = signal(false);
 	const setRenderReasonCapture = (v: boolean) => {
-		captureRenderReasons.$ = v;
+		captureRenderReasons.value = v;
 	};
 
 	// Highlight updates
-	const showUpdates = valoo(false);
+	const showUpdates = signal(false);
 
 	// Selection
-	const activeCommitIdx = valoo(0);
-	const selectedNodeId = valoo(0);
+	const activeCommitIdx = signal(0);
+	const selectedNodeId = signal(0);
 	const activeCommit = watch(() => {
-		return (commits.$.length > 0 && commits.$[activeCommitIdx.$]) || null;
+		return (
+			(commits.value.length > 0 && commits.value[activeCommitIdx.value]) || null
+		);
 	});
 	const selectedNode = watch(() => {
-		return activeCommit.$ != null
-			? activeCommit.$.nodes.get(selectedNodeId.$) || null
+		return activeCommit.value != null
+			? activeCommit.value.nodes.get(selectedNodeId.value) || null
 			: null;
 	});
 
 	// Flamegraph
-	const flamegraphType = valoo(FlamegraphType.FLAMEGRAPH);
+	const flamegraphType = signal(FlamegraphType.FLAMEGRAPH);
 	flamegraphType.on(() => {
-		selectedNodeId.$ = activeCommit.$
-			? getCommitInitalSelectNodeId(activeCommit.$, flamegraphType.$)
+		selectedNodeId.value = activeCommit.value
+			? getCommitInitalSelectNodeId(activeCommit.value, flamegraphType.value)
 			: -1;
 	});
 
 	// Recording
-	const isRecording = valoo(false);
+	const isRecording = signal(false);
 	isRecording.on(v => {
 		// Clear current selection and profiling data when
 		// a new recording starts.
 		if (v) {
-			commits.$ = [];
-			activeCommitIdx.$ = 0;
-			selectedNodeId.$ = 0;
+			commits.value = [];
+			activeCommitIdx.value = 0;
+			selectedNodeId.value = 0;
 		} else {
 			// Reset selection when recording stopped
 			// and new profiling data was collected.
-			if (commits.$.length > 0) {
-				selectedNodeId.$ = getCommitInitalSelectNodeId(
-					commits.$[0],
-					flamegraphType.$,
+			if (commits.value.length > 0) {
+				selectedNodeId.value = getCommitInitalSelectNodeId(
+					commits.value[0],
+					flamegraphType.value,
 				);
 			}
 		}
@@ -157,11 +159,11 @@ export function createProfiler(): ProfilerState {
 
 	// Render reasons
 	const activeReason = watch(() => {
-		if (activeCommit.$ !== null) {
-			const commitId = activeCommit.$.commitRootId;
-			const reason = renderReasons.$.get(commitId);
+		if (activeCommit.value !== null) {
+			const commitId = activeCommit.value.commitRootId;
+			const reason = renderReasons.value.get(commitId);
 			if (reason) {
-				return reason.get(selectedNodeId.$) || null;
+				return reason.get(selectedNodeId.value) || null;
 			}
 		}
 
@@ -170,13 +172,13 @@ export function createProfiler(): ProfilerState {
 
 	// FlamegraphNode
 	const flamegraphNodes = watch<Map<ID, NodeTransform>>(() => {
-		const commit = activeCommit.$;
-		if (!commit || flamegraphType.$ !== FlamegraphType.FLAMEGRAPH) {
+		const commit = activeCommit.value;
+		if (!commit || flamegraphType.value !== FlamegraphType.FLAMEGRAPH) {
 			return new Map();
 		}
 
-		for (let i = activeCommitIdx.$ - 1; i >= 0; i--) {
-			if (i >= commits.$.length) {
+		for (let i = activeCommitIdx.value - 1; i >= 0; i--) {
+			if (i >= commits.value.length) {
 				return new Map();
 			}
 		}
@@ -185,8 +187,8 @@ export function createProfiler(): ProfilerState {
 	});
 
 	const rankedNodes = watch<NodeTransform[]>(() => {
-		const commit = activeCommit.$;
-		if (!commit || flamegraphType.$ !== FlamegraphType.RANKED) {
+		const commit = activeCommit.value;
+		if (!commit || flamegraphType.value !== FlamegraphType.RANKED) {
 			return [];
 		}
 
@@ -216,14 +218,14 @@ export function createProfiler(): ProfilerState {
 }
 
 export function stopProfiling(state: ProfilerState) {
-	state.isRecording.$ = false;
-	state.activeCommitIdx.$ = 0;
-	state.selectedNodeId.$ = -1;
+	state.isRecording.value = false;
+	state.activeCommitIdx.value = 0;
+	state.selectedNodeId.value = -1;
 }
 
 export function resetProfiler(state: ProfilerState) {
 	stopProfiling(state);
-	state.commits.$ = [];
+	state.commits.value = [];
 }
 
 export function recordProfilerCommit(
@@ -242,7 +244,7 @@ export function recordProfilerCommit(
 	const rootId = getRoot(tree, commitRootId);
 
 	// Find previous commit to copy over timing data later
-	const commits = profiler.commits.$;
+	const commits = profiler.commits.value;
 	let prevCommit: CommitData | undefined;
 	for (let i = commits.length - 1; i >= 0; i--) {
 		if (commits[i].rootId === rootId) {
diff --git a/src/view/components/profiler/flamegraph/FlameGraph.tsx b/src/view/components/profiler/flamegraph/FlameGraph.tsx
index 9780552..a04f387 100644
--- a/src/view/components/profiler/flamegraph/FlameGraph.tsx
+++ b/src/view/components/profiler/flamegraph/FlameGraph.tsx
@@ -19,11 +19,13 @@ export function FlameGraph() {
 	const store = useStore();
 	const [canvasWidth, setCanvasWidth] = useState(-1);
 
-	const displayType = useObserver(() => store.profiler.flamegraphType.$);
-	const selected = useObserver(() => store.profiler.selectedNode.$ || EMPTY);
-	const commit = useObserver(() => store.profiler.activeCommit.$);
-	const isRecording = useObserver(() => store.profiler.isRecording.$);
-	const showDebug = useObserver(() => store.debugMode.$);
+	const displayType = useObserver(() => store.profiler.flamegraphType.value);
+	const selected = useObserver(
+		() => store.profiler.selectedNode.value || EMPTY,
+	);
+	const commit = useObserver(() => store.profiler.activeCommit.value);
+	const isRecording = useObserver(() => store.profiler.isRecording.value);
+	const showDebug = useObserver(() => store.debugMode.value);
 
 	const ref = useRef<HTMLDivElement>(null);
 	useEffect(() => {
@@ -45,7 +47,7 @@ export function FlameGraph() {
 
 	const onSelect = useCallback(
 		(id: number) => {
-			store.profiler.selectedNodeId.$ = id;
+			store.profiler.selectedNodeId.value = id;
 			store.selection.selectById(id);
 		},
 		[store],
diff --git a/src/view/components/profiler/flamegraph/FlameGraphMode.tsx b/src/view/components/profiler/flamegraph/FlameGraphMode.tsx
index 68a4fe1..d8f4891 100644
--- a/src/view/components/profiler/flamegraph/FlameGraphMode.tsx
+++ b/src/view/components/profiler/flamegraph/FlameGraphMode.tsx
@@ -7,11 +7,11 @@ import { Icon } from "../../icons";
 
 export function FlameGraphMode() {
 	const store = useStore();
-	const type = useObserver(() => store.profiler.flamegraphType.$);
-	const disabled = useObserver(() => !store.profiler.isSupported.$);
+	const type = useObserver(() => store.profiler.flamegraphType.value);
+	const disabled = useObserver(() => !store.profiler.isSupported.value);
 
 	const onClick = useCallback((value: string) => {
-		store.profiler.flamegraphType.$ = value as any;
+		store.profiler.flamegraphType.value = value as any;
 	}, []);
 
 	return (
diff --git a/src/view/components/profiler/flamegraph/modes/FlamegraphLayout.tsx b/src/view/components/profiler/flamegraph/modes/FlamegraphLayout.tsx
index bfcb70f..8105f12 100644
--- a/src/view/components/profiler/flamegraph/modes/FlamegraphLayout.tsx
+++ b/src/view/components/profiler/flamegraph/modes/FlamegraphLayout.tsx
@@ -30,8 +30,8 @@ export function FlamegraphLayout({
 	containerRef,
 }: FlamegraphLayoutProps) {
 	const store = useStore();
-	const data = useObserver(() => store.profiler.flamegraphNodes.$);
-	const filterHoc = useObserver(() => store.filter.filterHoc.$);
+	const data = useObserver(() => store.profiler.flamegraphNodes.value);
+	const filterHoc = useObserver(() => store.filter.filterHoc.value);
 
 	const placed = useMemo(
 		() =>
diff --git a/src/view/components/profiler/flamegraph/ranked/RankedLayout.tsx b/src/view/components/profiler/flamegraph/ranked/RankedLayout.tsx
index c4ff265..88a02ac 100644
--- a/src/view/components/profiler/flamegraph/ranked/RankedLayout.tsx
+++ b/src/view/components/profiler/flamegraph/ranked/RankedLayout.tsx
@@ -36,8 +36,8 @@ export function RankedLayout({
 }: RankedLayoutProps) {
 	// Convert node tree to position data
 	const store = useStore();
-	const data = useObserver(() => store.profiler.rankedNodes.$);
-	const filterHoc = useObserver(() => store.filter.filterHoc.$);
+	const data = useObserver(() => store.profiler.rankedNodes.value);
+	const filterHoc = useObserver(() => store.filter.filterHoc.value);
 
 	const placed = useMemo(
 		() => placeRanked(commit.selfDurations, data, selected, canvasWidth),
diff --git a/src/view/components/settings/Settings.tsx b/src/view/components/settings/Settings.tsx
index 41ffcdf..56bc537 100644
--- a/src/view/components/settings/Settings.tsx
+++ b/src/view/components/settings/Settings.tsx
@@ -9,14 +9,16 @@ import { PageLayout } from "../SidebarLayout";
 
 export function Settings() {
 	const store = useStore();
-	const theme = useObserver(() => store.theme.$);
+	const theme = useObserver(() => store.theme.value);
 	const renderReasons = useObserver(
-		() => store.profiler.captureRenderReasons.$,
+		() => store.profiler.captureRenderReasons.value,
 	);
-	const highlightUpdates = useObserver(() => store.profiler.highlightUpdates.$);
-	const debugMode = useObserver(() => store.debugMode.$);
+	const highlightUpdates = useObserver(
+		() => store.profiler.highlightUpdates.value,
+	);
+	const debugMode = useObserver(() => store.debugMode.value);
 
-	const setTheme = useCallback((v: Theme) => (store.theme.$ = v), []);
+	const setTheme = useCallback((v: Theme) => (store.theme.value = v), []);
 
 	return (
 		<PageLayout>
@@ -25,8 +27,8 @@ export function Settings() {
 					<Checkbox
 						checked={highlightUpdates}
 						onChange={() => {
-							const value = !store.profiler.highlightUpdates.$;
-							store.profiler.highlightUpdates.$ = value;
+							const value = !store.profiler.highlightUpdates.value;
+							store.profiler.highlightUpdates.value = value;
 							store.notify(
 								value ? "start-highlight-updates" : "stop-highlight-updates",
 								null,
@@ -76,7 +78,7 @@ export function Settings() {
 					<div class="settings-tab-checkbox">
 						<Checkbox
 							checked={debugMode}
-							onChange={() => (store.debugMode.$ = !store.debugMode.$)}
+							onChange={() => (store.debugMode.value = !store.debugMode.value)}
 							testId="toggle-debug-mode"
 						>
 							Toggle debug mode
diff --git a/src/view/components/sidebar/DebugNodeNavTree.tsx b/src/view/components/sidebar/DebugNodeNavTree.tsx
index ccc58a5..b7c99d9 100644
--- a/src/view/components/sidebar/DebugNodeNavTree.tsx
+++ b/src/view/components/sidebar/DebugNodeNavTree.tsx
@@ -4,9 +4,9 @@ import { SidebarPanel, Empty } from "./SidebarPanel";
 
 export function DebugNodeNavTree() {
 	const store = useStore();
-	const selected = useObserver(() => store.selection.selected.$);
+	const selected = useObserver(() => store.selection.selected.value);
 	const nodes = useObserver(() =>
-		store.nodeList.$.map(id => store.nodes.$.get(id)!),
+		store.nodeList.value.map(id => store.nodes.value.get(id)!),
 	);
 
 	return (
@@ -21,7 +21,7 @@ export function DebugNodeNavTree() {
 								key={node.id}
 								class="rendered-at-item"
 								data-active={selected === node.id}
-								onClick={() => (store.profiler.selectedNodeId.$ = node.id)}
+								onClick={() => (store.profiler.selectedNodeId.value = node.id)}
 							>
 								<span style="display: flex; justify-content: space-between; width: 100%">
 									<span>
diff --git a/src/view/components/sidebar/DebugTreeStats.tsx b/src/view/components/sidebar/DebugTreeStats.tsx
index c73326e..3263010 100644
--- a/src/view/components/sidebar/DebugTreeStats.tsx
+++ b/src/view/components/sidebar/DebugTreeStats.tsx
@@ -4,7 +4,7 @@ import { SidebarPanel } from "./SidebarPanel";
 
 export function DebugTreeStats() {
 	const store = useStore();
-	const nodeList = useObserver(() => store.nodeList.$);
+	const nodeList = useObserver(() => store.nodeList.value);
 
 	return (
 		<SidebarPanel title="Debug Tree Stats" testId="tree-debug-stats">
diff --git a/src/view/components/sidebar/Sidebar.tsx b/src/view/components/sidebar/Sidebar.tsx
index 4e1a3ec..ee465f4 100644
--- a/src/view/components/sidebar/Sidebar.tsx
+++ b/src/view/components/sidebar/Sidebar.tsx
@@ -10,11 +10,11 @@ import { HocPanel } from "./HocPanel";
 
 export function Sidebar() {
 	const store = useStore();
-	const showDebug = useObserver(() => store.debugMode.$);
-	const inspect = useObserver(() => store.inspectData.$);
+	const showDebug = useObserver(() => store.debugMode.value);
+	const inspect = useObserver(() => store.inspectData.value);
 	const hocs = useObserver(() => {
-		if (store.inspectData.$) {
-			const node = store.nodes.$.get(store.inspectData.$.id);
+		if (store.inspectData.value) {
+			const node = store.nodes.value.get(store.inspectData.value.id);
 			return node ? node.hocs : null;
 		}
 		return null;
diff --git a/src/view/components/sidebar/SidebarActions.tsx b/src/view/components/sidebar/SidebarActions.tsx
index d14ed88..1f54cb9 100644
--- a/src/view/components/sidebar/SidebarActions.tsx
+++ b/src/view/components/sidebar/SidebarActions.tsx
@@ -11,7 +11,7 @@ export function SidebarActions() {
 	const store = useStore();
 	const emit = useEmitter();
 	const node = useObserver(
-		() => store.nodes.$.get(store.selection.selected.$) || null,
+		() => store.nodes.value.get(store.selection.selected.value) || null,
 	);
 	const log = useCallback(() => {
 		if (node) emit("log", { id: node.id, children: node.children });
@@ -36,9 +36,9 @@ export function SidebarActions() {
 			suspended: false,
 		};
 
-		if (store.inspectData.$) {
-			state.canSuspend = store.inspectData.$.canSuspend;
-			state.suspended = store.inspectData.$.suspended;
+		if (store.inspectData.value) {
+			state.canSuspend = store.inspectData.value.canSuspend;
+			state.suspended = store.inspectData.value.suspended;
 		}
 
 		return state;
diff --git a/src/view/components/sidebar/inspect/PropsPanel.tsx b/src/view/components/sidebar/inspect/PropsPanel.tsx
index edfc5fa..b8da197 100644
--- a/src/view/components/sidebar/inspect/PropsPanel.tsx
+++ b/src/view/components/sidebar/inspect/PropsPanel.tsx
@@ -2,7 +2,7 @@ import { h, Fragment } from "preact";
 import { ElementProps, ChangeFn } from "./ElementProps";
 import { SidebarPanel, Empty } from "../SidebarPanel";
 import { NewProp } from "./NewProp";
-import { Observable } from "../../../valoo";
+import { Signal } from "../../../valoo";
 import { PropData } from "./parseProps";
 import { useObserver, useStore } from "../../../store/react-bindings";
 import { Message } from "../../Message/Message";
@@ -10,8 +10,8 @@ import { Message } from "../../Message/Message";
 export interface Props {
 	label: string;
 	isOptional?: boolean;
-	uncollapsed: Observable<string[]>;
-	items: Observable<PropData[]>;
+	uncollapsed: Signal<string[]>;
+	items: Signal<PropData[]>;
 	canAddNew?: boolean;
 	onChange: ChangeFn;
 	onCopy?: () => void;
@@ -19,10 +19,10 @@ export interface Props {
 
 export function PropsPanel(props: Props) {
 	const { label, onCopy, onChange, canAddNew } = props;
-	const uncollapsed = useObserver(() => props.uncollapsed.$);
-	const items = useObserver(() => props.items.$);
+	const uncollapsed = useObserver(() => props.uncollapsed.value);
+	const items = useObserver(() => props.items.value);
 	const store = useStore();
-	const isSupported = useObserver(() => store.supports.hooks.$);
+	const isSupported = useObserver(() => store.supports.hooks.value);
 
 	return (
 		<SidebarPanel title={label} onCopy={onCopy} testId={label}>
@@ -33,7 +33,7 @@ export function PropsPanel(props: Props) {
 						items={items}
 						onChange={onChange}
 						onCollapse={id => {
-							const idx = props.uncollapsed.$.indexOf(id);
+							const idx = props.uncollapsed.value.indexOf(id);
 							props.uncollapsed.update(v => {
 								idx > -1 ? v.splice(idx, 1) : v.push(id);
 							});
diff --git a/src/view/components/stats/StatsPanel.tsx b/src/view/components/stats/StatsPanel.tsx
index e16e863..0e9a7c9 100644
--- a/src/view/components/stats/StatsPanel.tsx
+++ b/src/view/components/stats/StatsPanel.tsx
@@ -14,12 +14,12 @@ import { Icon } from "../icons";
 
 export function StatsRecordBtn() {
 	const store = useStore();
-	const isRecording = useObserver(() => store.stats.isRecording.$);
+	const isRecording = useObserver(() => store.stats.isRecording.value);
 
 	const onClick = useCallback(() => {
 		const { isRecording } = store.stats;
-		const v = !isRecording.$;
-		isRecording.$ = v;
+		const v = !isRecording.value;
+		isRecording.value = v;
 
 		if (v) {
 			store.emit("start-stats-recording", null);
@@ -44,15 +44,15 @@ export function StatsRecordBtn() {
 
 export function StatsPanel() {
 	const store = useStore();
-	const stats = useObserver(() => store.stats.data.$);
-	const isRecording = useObserver(() => store.stats.isRecording.$);
+	const stats = useObserver(() => store.stats.data.value);
+	const isRecording = useObserver(() => store.stats.isRecording.value);
 	const onReloadAndRecordStats = useCallback(() => {
-		store.stats.isRecording.$ = true;
+		store.stats.isRecording.value = true;
 		store.emit("reload-and-record-stats", null);
 	}, []);
 	const onReset = useCallback(() => {
-		store.stats.data.$ = null;
-		store.stats.isRecording.$ = false;
+		store.stats.data.value = null;
+		store.stats.isRecording.value = false;
 		store.emit("stop-profiling", null);
 	}, [store]);
 
diff --git a/src/view/store/collapser.ts b/src/view/store/collapser.ts
index 31e1d34..664a340 100644
--- a/src/view/store/collapser.ts
+++ b/src/view/store/collapser.ts
@@ -1,9 +1,9 @@
 import { AppCtx, useObserver } from "./react-bindings";
-import { Observable } from "../valoo";
+import { Signal } from "../valoo";
 import { useContext } from "preact/hooks";
 
 export interface Collapser<T> {
-	collapsed: Observable<Set<T>>;
+	collapsed: Signal<Set<T>>;
 	toggle: (item: T) => void;
 	collapseNode: (item: T, shouldCollapse: boolean) => void;
 }
@@ -11,16 +11,14 @@ export interface Collapser<T> {
 /**
  * The Collapser deals with hiding sections in a tree view
  */
-export function createCollapser<T>(
-	collapsed: Observable<Set<T>>,
-): Collapser<T> {
+export function createCollapser<T>(collapsed: Signal<Set<T>>): Collapser<T> {
 	const collapseNode = (id: T, shouldCollapse: boolean) => {
 		collapsed.update(s => {
 			shouldCollapse ? s.add(id) : s.delete(id);
 		});
 	};
 
-	const toggle = (id: T) => collapseNode(id, !collapsed.$.has(id));
+	const toggle = (id: T) => collapseNode(id, !collapsed.value.has(id));
 
 	return {
 		collapsed,
@@ -31,6 +29,6 @@ export function createCollapser<T>(
 
 export function useCollapser() {
 	const c = useContext(AppCtx).collapser;
-	const collapsed = useObserver(() => c.collapsed.$);
+	const collapsed = useObserver(() => c.collapsed.value);
 	return { collapsed, collapseNode: c.collapseNode, toggle: c.toggle };
 }
diff --git a/src/view/store/filter.ts b/src/view/store/filter.ts
index c5d4b64..c7e309e 100644
--- a/src/view/store/filter.ts
+++ b/src/view/store/filter.ts
@@ -1,4 +1,4 @@
-import { valoo } from "../valoo";
+import { signal } from "../valoo";
 import { escapeStringRegexp } from "./utils";
 import { RawFilterState, TypeFilterValue } from "../../adapter/adapter/filter";
 
@@ -18,24 +18,24 @@ export function createFilterStore(
 		regex: [] as RawFilter[],
 	};
 
-	const filters = valoo<RawFilter[]>(defaults.regex);
-	const filterFragment = valoo(defaults.fragment);
-	const filterDom = valoo(defaults.dom);
-	const filterHoc = valoo(defaults.hoc);
-	const filterRoot = valoo(defaults.root);
+	const filters = signal<RawFilter[]>(defaults.regex);
+	const filterFragment = signal(defaults.fragment);
+	const filterDom = signal(defaults.dom);
+	const filterHoc = signal(defaults.hoc);
+	const filterRoot = signal(defaults.root);
 
 	const submit = () => {
 		const s: RawFilterState = {
 			regex: [],
 			type: {
-				fragment: filterFragment.$,
-				dom: filterDom.$,
-				hoc: filterHoc.$,
-				root: filterRoot.$,
+				fragment: filterFragment.value,
+				dom: filterDom.value,
+				hoc: filterHoc.value,
+				root: filterRoot.value,
 			},
 		};
 
-		filters.$.forEach(x => {
+		filters.value.forEach(x => {
 			s.regex.push({ value: escapeStringRegexp(x.value), enabled: x.enabled });
 		});
 		onSubmit("update-filter", s);
@@ -43,19 +43,19 @@ export function createFilterStore(
 
 	const restore = (state: RawFilterState) => {
 		try {
-			filterFragment.$ = !!state.type.fragment;
-			filterDom.$ = !!state.type.dom;
-			filterHoc.$ = !!state.type.hoc;
-			filterRoot.$ = !!state.type.root;
-			filters.$ = state.regex;
+			filterFragment.value = !!state.type.fragment;
+			filterDom.value = !!state.type.dom;
+			filterHoc.value = !!state.type.hoc;
+			filterRoot.value = !!state.type.root;
+			filters.value = state.regex;
 
 			// Refetch component tree if filters are not the default ones
 			if (
-				defaults.fragment !== filterFragment.$ ||
-				defaults.dom !== filterDom.$ ||
-				defaults.hoc !== filterHoc.$ ||
-				defaults.root !== filterRoot.$ ||
-				filters.$.some(f => f.enabled)
+				defaults.fragment !== filterFragment.value ||
+				defaults.dom !== filterDom.value ||
+				defaults.hoc !== filterHoc.value ||
+				defaults.root !== filterRoot.value ||
+				filters.value.some(f => f.enabled)
 			) {
 				submit();
 			}
@@ -74,13 +74,13 @@ export function createFilterStore(
 		setEnabled(filter: RawFilter | TypeFilterValue, v: boolean) {
 			if (typeof filter === "string") {
 				if (filter === "dom") {
-					filterDom.$ = v;
+					filterDom.value = v;
 				} else if (filter === "fragment") {
-					filterFragment.$ = v;
+					filterFragment.value = v;
 				} else if (filter === "hoc") {
-					filterHoc.$ = v;
+					filterHoc.value = v;
 				} else if (filter === "root") {
-					filterRoot.$ = v;
+					filterRoot.value = v;
 				}
 			} else {
 				filter.enabled = v;
@@ -100,7 +100,7 @@ export function createFilterStore(
 			});
 		},
 		remove(filter: RawFilter) {
-			const idx = filters.$.indexOf(filter);
+			const idx = filters.value.indexOf(filter);
 			if (idx > -1) {
 				filters.update(v => {
 					v.splice(idx, 1);
diff --git a/src/view/store/index.ts b/src/view/store/index.ts
index 16bdac9..041a015 100644
--- a/src/view/store/index.ts
+++ b/src/view/store/index.ts
@@ -1,4 +1,4 @@
-import { valoo, watch } from "../valoo";
+import { signal, watch } from "../valoo";
 import { createSearchStore } from "./search";
 import { createFilterStore } from "./filter";
 import { flattenChildren } from "../components/tree/windowing";
@@ -17,96 +17,106 @@ export function createStore(): Store {
 		listeners.forEach(fn => fn && fn(name, data));
 	};
 
-	const debugMode = valoo(!!process.env.DEBUG);
+	const debugMode = signal(!!process.env.DEBUG);
 
-	const nodes = valoo<Map<ID, DevNode>>(new Map());
-	const roots = valoo<ID[]>([]);
+	const nodes = signal<Map<ID, DevNode>>(new Map());
+	const roots = signal<ID[]>([]);
 
 	// Toggle
-	const isPicking = valoo<boolean>(false);
+	const isPicking = signal<boolean>(false);
 	const filterState = createFilterStore(notify);
 
 	// List
-	const collapsed = valoo(new Set<ID>());
+	const collapsed = signal(new Set<ID>());
 	const collapser = createCollapser<ID>(collapsed);
 
 	const nodeList = watch(() => {
-		return roots.$.map(root => {
-			const items = flattenChildren<ID, DevNode>(nodes.$, root, id =>
-				collapser.collapsed.$.has(id),
-			);
+		return roots.value
+			.map(root => {
+				const items = flattenChildren<ID, DevNode>(nodes.value, root, id =>
+					collapser.collapsed.value.has(id),
+				);
 
-			if (filterState.filterRoot.$) {
-				return items.slice(1);
-			}
+				if (filterState.filterRoot.value) {
+					return items.slice(1);
+				}
 
-			return items;
-		}).reduce((acc, val) => acc.concat(val), []);
+				return items;
+			})
+			.reduce((acc, val) => acc.concat(val), []);
 	});
 
 	// Sidebar
 	const sidebar = {
 		props: {
-			uncollapsed: valoo<string[]>([]),
-			items: valoo<PropData[]>([]),
+			uncollapsed: signal<string[]>([]),
+			items: signal<PropData[]>([]),
 		},
 		state: {
-			uncollapsed: valoo<string[]>([]),
-			items: valoo<PropData[]>([]),
+			uncollapsed: signal<string[]>([]),
+			items: signal<PropData[]>([]),
 		},
 		context: {
-			uncollapsed: valoo<string[]>([]),
-			items: valoo<PropData[]>([]),
+			uncollapsed: signal<string[]>([]),
+			items: signal<PropData[]>([]),
 		},
 		hooks: {
-			uncollapsed: valoo<string[]>([]),
-			items: valoo<PropData[]>([]),
+			uncollapsed: signal<string[]>([]),
+			items: signal<PropData[]>([]),
 		},
 	};
 
-	const inspectData = valoo<InspectData | null>(null);
+	const inspectData = signal<InspectData | null>(null);
 
 	watch(() => {
-		const data = inspectData.$ ? inspectData.$.props : null;
-		sidebar.props.items.$ = parseObjectState(data, sidebar.props.uncollapsed.$);
+		const data = inspectData.value ? inspectData.value.props : null;
+		sidebar.props.items.value = parseObjectState(
+			data,
+			sidebar.props.uncollapsed.value,
+		);
 	});
 	watch(() => {
-		const data = inspectData.$ ? inspectData.$.state : null;
-		sidebar.state.items.$ = parseObjectState(data, sidebar.state.uncollapsed.$);
+		const data = inspectData.value ? inspectData.value.state : null;
+		sidebar.state.items.value = parseObjectState(
+			data,
+			sidebar.state.uncollapsed.value,
+		);
 	});
 	watch(() => {
-		const data = inspectData.$ ? inspectData.$.context : null;
-		sidebar.context.items.$ = parseObjectState(
+		const data = inspectData.value ? inspectData.value.context : null;
+		sidebar.context.items.value = parseObjectState(
 			data,
-			sidebar.context.uncollapsed.$,
+			sidebar.context.uncollapsed.value,
 		);
 	});
 
-	const supportsHooks = valoo(false);
+	const supportsHooks = signal(false);
 	watch(() => {
 		if (supportsHooks) {
 			const items =
-				inspectData.$ && inspectData.$.hooks ? inspectData.$.hooks : [];
-			sidebar.hooks.items.$ = filterCollapsed(
+				inspectData.value && inspectData.value.hooks
+					? inspectData.value.hooks
+					: [];
+			sidebar.hooks.items.value = filterCollapsed(
 				items,
-				sidebar.hooks.uncollapsed.$,
+				sidebar.hooks.uncollapsed.value,
 			).slice(1);
 		}
 	});
 
 	const selection = createSelectionStore(nodeList);
-	const stats = valoo(null);
+	const stats = signal(null);
 
 	return {
 		supports: {
 			hooks: supportsHooks,
 		},
 		stats: {
-			isRecording: valoo(false),
+			isRecording: signal(false),
 			data: stats,
 		},
 		debugMode,
-		activePanel: valoo(Panel.ELEMENTS),
+		activePanel: signal(Panel.ELEMENTS),
 		profiler: createProfiler(),
 		notify,
 		nodeList,
@@ -118,14 +128,14 @@ export function createStore(): Store {
 		search: createSearchStore(nodes, nodeList),
 		filter: filterState,
 		selection,
-		theme: valoo<Theme>("auto"),
+		theme: signal<Theme>("auto"),
 		sidebar,
 		clear() {
-			roots.$ = [];
-			nodes.$ = new Map();
-			selection.selected.$ = -1;
-			collapser.collapsed.$ = new Set();
-			stats.$ = null;
+			roots.value = [];
+			nodes.value = new Map();
+			selection.selected.value = -1;
+			collapser.collapsed.value = new Set();
+			stats.value = null;
 		},
 		subscribe(fn) {
 			const idx = listeners.push(fn);
diff --git a/src/view/store/react-bindings.ts b/src/view/store/react-bindings.ts
index c515e1e..23e991e 100644
--- a/src/view/store/react-bindings.ts
+++ b/src/view/store/react-bindings.ts
@@ -2,7 +2,7 @@ import { createContext } from "preact";
 import { useEffect, useContext, useState, useMemo, useRef } from "preact/hooks";
 import { Store } from "./types";
 import { EmitFn } from "../../adapter/hook";
-import { watch, Observable } from "../valoo";
+import { watch, Signal } from "../valoo";
 
 // Make sure we're accessing the right window object. The global window
 // reference is not the same and won't trigger any "resize" (and likely
@@ -19,7 +19,7 @@ export function useObserver<T>(fn: () => T): T {
 	const [_, set] = useState(0);
 	const count = useRef(0);
 	const tmp = useRef<any>(null as any);
-	const ref = useRef<Observable<T>>(tmp.current || (tmp.current = watch(fn)));
+	const ref = useRef<Signal<T>>(tmp.current || (tmp.current = watch(fn)));
 
 	const dispose = useMemo(() => {
 		const disp = ref.current.on(() => {
@@ -35,5 +35,5 @@ export function useObserver<T>(fn: () => T): T {
 		return () => dispose();
 	}, []);
 
-	return ref.current.$;
+	return ref.current.value;
 }
diff --git a/src/view/store/search.ts b/src/view/store/search.ts
index ac23553..63c3bec 100644
--- a/src/view/store/search.ts
+++ b/src/view/store/search.ts
@@ -1,4 +1,4 @@
-import { Observable, valoo } from "../valoo";
+import { Signal, signal } from "../valoo";
 import { useStore, useObserver } from "./react-bindings";
 import { escapeStringRegexp } from "./utils";
 import { ID, DevNode } from "./types";
@@ -19,34 +19,34 @@ export function createRegex(s: string): RegExp {
 }
 
 export function createSearchStore(
-	items: Observable<Map<ID, DevNode>>,
-	list: Observable<ID[]>,
+	items: Signal<Map<ID, DevNode>>,
+	list: Signal<ID[]>,
 ) {
-	const value = valoo("");
-	const selected = valoo(0);
-	const selectedIdx = valoo(-1);
-	const regex = valoo<RegExp | null>(null);
-	const match = valoo<number[]>([]);
-	const count = valoo(0);
+	const searchValue = signal("");
+	const selected = signal(0);
+	const selectedIdx = signal(-1);
+	const regex = signal<RegExp | null>(null);
+	const match = signal<number[]>([]);
+	const count = signal(0);
 
 	const onChange = (s: string) => {
-		value.$ = s;
+		searchValue.value = s;
 
-		match.$ = [];
+		match.value = [];
 
 		if (s === "") {
-			regex.$ = null;
-			count.$ = 0;
-			selected.$ = 0;
+			regex.value = null;
+			count.value = 0;
+			selected.value = 0;
 			return;
 		}
 
 		const reg = createRegex(s);
-		regex.$ = reg;
+		regex.value = reg;
 
 		const ids: number[] = [];
-		list.$.forEach(id => {
-			const node = items.$.get(id);
+		list.value.forEach(id => {
+			const node = items.value.get(id);
 			if (
 				node &&
 				(reg.test(node.name) || (node.hocs && node.hocs.some(h => reg.test(h))))
@@ -56,33 +56,33 @@ export function createSearchStore(
 		});
 
 		if (ids.length > 0) {
-			selected.$ = 0;
+			selected.value = 0;
 		}
-		count.$ = ids.length;
-		match.$ = ids;
+		count.value = ids.length;
+		match.value = ids;
 	};
 
 	const reset = () => {
-		selectedIdx.$ = -1;
+		selectedIdx.value = -1;
 		onChange("");
 	};
 
 	function go(n: number) {
-		if (n < 0) n = match.$.length - 1;
-		else if (n > match.$.length - 1) n = 0;
-		selected.$ = n;
-		selectedIdx.$ = list.$.findIndex(id => match.$[n] === id);
+		if (n < 0) n = match.value.length - 1;
+		else if (n > match.value.length - 1) n = 0;
+		selected.value = n;
+		selectedIdx.value = list.value.findIndex(id => match.value[n] === id);
 	}
 
-	const selectNext = () => go(selected.$ + 1);
-	const selectPrev = () => go(selected.$ - 1);
+	const selectNext = () => go(selected.value + 1);
+	const selectPrev = () => go(selected.value - 1);
 
 	return {
 		selected,
 		selectedIdx,
 		regex,
 		count,
-		value,
+		searchValue,
 		match,
 		reset,
 		onChange,
@@ -93,13 +93,13 @@ export function createSearchStore(
 
 export function useSearch() {
 	const { search: s } = useStore();
-	const match = useObserver(() => s.match.$);
-	const value = useObserver(() => s.value.$);
-	const marked = useObserver(() => s.selected.$);
-	const regex = useObserver(() => s.regex.$);
-	const count = useObserver(() => s.count.$);
-	const selected = useObserver(() => s.selected.$);
-	const selectedId = useObserver(() => s.match.$[s.selected.$]);
+	const match = useObserver(() => s.match.value);
+	const value = useObserver(() => s.searchValue.value);
+	const marked = useObserver(() => s.selected.value);
+	const regex = useObserver(() => s.regex.value);
+	const count = useObserver(() => s.count.value);
+	const selected = useObserver(() => s.selected.value);
+	const selectedId = useObserver(() => s.match.value[s.selected.value]);
 	return {
 		count,
 		selected,
diff --git a/src/view/store/selection.ts b/src/view/store/selection.ts
index a0da223..4ed5017 100644
--- a/src/view/store/selection.ts
+++ b/src/view/store/selection.ts
@@ -1,5 +1,5 @@
 import { AppCtx, useObserver } from "./react-bindings";
-import { valoo, Observable } from "../valoo";
+import { signal, Signal } from "../valoo";
 import { clamp } from "../components/tree/windowing";
 import { useContext } from "preact/hooks";
 import { ID } from "./types";
@@ -7,21 +7,21 @@ import { ID } from "./types";
 /**
  * Manages selection state of the TreeView.
  */
-export function createSelectionStore(list: Observable<ID[]>) {
-	const selected = valoo<ID>(list.$.length > 0 ? list.$[0] : -1);
-	const selectedIdx = valoo(0);
+export function createSelectionStore(list: Signal<ID[]>) {
+	const selected = signal<ID>(list.value.length > 0 ? list.value[0] : -1);
+	const selectedIdx = signal(0);
 
 	const selectByIndex = (idx: number) => {
-		const n = clamp(idx, list.$.length - 1);
-		selected.$ = list.$[n];
-		selectedIdx.$ = n;
+		const n = clamp(idx, list.value.length - 1);
+		selected.value = list.value[n];
+		selectedIdx.value = n;
 	};
 
-	const selectNext = () => selectByIndex(selectedIdx.$ + 1);
-	const selectPrev = () => selectByIndex(selectedIdx.$ - 1);
+	const selectNext = () => selectByIndex(selectedIdx.value + 1);
+	const selectPrev = () => selectByIndex(selectedIdx.value - 1);
 
 	const selectById = (id: ID) => {
-		const idx = list.$.findIndex(x => x === id);
+		const idx = list.value.findIndex(x => x === id);
 		selectByIndex(idx);
 	};
 
@@ -37,8 +37,8 @@ export function createSelectionStore(list: Observable<ID[]>) {
 
 export function useSelection() {
 	const sel = useContext(AppCtx).selection;
-	const selected = useObserver(() => sel.selected.$);
-	const selectedIdx = useObserver(() => sel.selectedIdx.$);
+	const selected = useObserver(() => sel.selected.value);
+	const selectedIdx = useObserver(() => sel.selectedIdx.value);
 	return {
 		selected,
 		selectedIdx,
diff --git a/src/view/store/types.ts b/src/view/store/types.ts
index c4e23f7..6c2cd09 100644
--- a/src/view/store/types.ts
+++ b/src/view/store/types.ts
@@ -1,4 +1,4 @@
-import { Observable } from "../valoo";
+import { Signal } from "../valoo";
 import { InspectData } from "../../adapter/adapter/adapter";
 import { createSearchStore } from "./search";
 import { createFilterStore } from "./filter";
@@ -60,42 +60,42 @@ export type Tree = Map<ID, DevNode>;
 
 export interface Store {
 	supports: {
-		hooks: Observable<boolean>;
+		hooks: Signal<boolean>;
 	};
 	stats: {
-		isRecording: Observable<boolean>;
-		data: Observable<ParsedStats | null>;
+		isRecording: Signal<boolean>;
+		data: Signal<ParsedStats | null>;
 	};
-	debugMode: Observable<boolean>;
-	activePanel: Observable<Panel>;
+	debugMode: Signal<boolean>;
+	activePanel: Signal<Panel>;
 	notify: EmitFn;
 	profiler: ProfilerState;
-	isPicking: Observable<boolean>;
-	inspectData: Observable<InspectData | null>;
-	roots: Observable<ID[]>;
-	nodes: Observable<Tree>;
-	nodeList: Observable<ID[]>;
-	theme: Observable<Theme>;
+	isPicking: Signal<boolean>;
+	inspectData: Signal<InspectData | null>;
+	roots: Signal<ID[]>;
+	nodes: Signal<Tree>;
+	nodeList: Signal<ID[]>;
+	theme: Signal<Theme>;
 	search: ReturnType<typeof createSearchStore>;
 	filter: ReturnType<typeof createFilterStore>;
 	selection: ReturnType<typeof createSelectionStore>;
 	collapser: Collapser<ID>;
 	sidebar: {
 		props: {
-			uncollapsed: Observable<string[]>;
-			items: Observable<PropData[]>;
+			uncollapsed: Signal<string[]>;
+			items: Signal<PropData[]>;
 		};
 		state: {
-			uncollapsed: Observable<string[]>;
-			items: Observable<PropData[]>;
+			uncollapsed: Signal<string[]>;
+			items: Signal<PropData[]>;
 		};
 		context: {
-			uncollapsed: Observable<string[]>;
-			items: Observable<PropData[]>;
+			uncollapsed: Signal<string[]>;
+			items: Signal<PropData[]>;
 		};
 		hooks: {
-			uncollapsed: Observable<string[]>;
-			items: Observable<PropData[]>;
+			uncollapsed: Signal<string[]>;
+			items: Signal<PropData[]>;
 		};
 	};
 	clear(): void;
diff --git a/src/view/valoo.ts b/src/view/valoo.ts
index d554976..9141741 100644
--- a/src/view/valoo.ts
+++ b/src/view/valoo.ts
@@ -5,8 +5,8 @@ export interface SubscribeOptions {
 	init?: boolean;
 }
 
-export type Observable<T = any> = {
-	$: T;
+export type Signal<T = any> = {
+	value: T;
 	on(fn: (value: T) => void, options?: SubscribeOptions): Disposer;
 	update(fn?: (value: T) => T | void): T;
 	_disposers: Disposer[];
@@ -15,14 +15,14 @@ export type Observable<T = any> = {
 /**
  * We'll use this variable to track used dependencies
  */
-let tracking = new Set<Observable>();
+let tracking = new Set<Signal>();
 
-const dummy = new Set<Observable>();
+const dummy = new Set<Signal>();
 
 /**
  * Base observable primitive.
  */
-export function valoo<T>(v: T): Observable<T> {
+export function signal<T>(v: T): Signal<T> {
 	// In an earlier version we used a function to read and write values:
 	//
 	//   read: foo()
@@ -42,12 +42,12 @@ export function valoo<T>(v: T): Observable<T> {
 	//
 	// Long story short: This is why we're using getters and setters now.
 	const cb: Array<((v: T) => void) | null> = [];
-	const obs: Observable = {
-		get $(): T {
+	const obs: Signal = {
+		get value(): T {
 			tracking.add(obs);
 			return v;
 		},
-		set $(c: T) {
+		set value(c: T) {
 			v = c;
 
 			// Be careful not to track any observables in callbacks
@@ -68,7 +68,7 @@ export function valoo<T>(v: T): Observable<T> {
 		},
 		update(fn?: (v: T) => T | void) {
 			const res = fn ? fn(v) : undefined;
-			obs.$ = res !== undefined ? res : v;
+			obs.value = res !== undefined ? res : v;
 		},
 		_disposers: [],
 	};
@@ -80,12 +80,12 @@ export function valoo<T>(v: T): Observable<T> {
  * Track used observables automatically and re-execute the callback
  * whenever one of the dependencies changes.
  */
-export function watch<R>(fn: () => R): Observable<R> {
-	const out = valoo(null as any);
+export function watch<R>(fn: () => R): Signal<R> {
+	const out = signal(null as any);
 
 	// Perf: Don't allocate this in update, because it's much better
 	// to reuse the Set and just mutate + clear it for an update cycle.
-	const tracker = new Set<Observable>();
+	const tracker = new Set<Signal>();
 
 	const update = () => {
 		const oldTracker = tracking;
@@ -96,7 +96,7 @@ export function watch<R>(fn: () => R): Observable<R> {
 		out._disposers = [];
 
 		// Call the actual function
-		out.$ = fn();
+		out.value = fn();
 
 		// Resubscribe to listeners
 		tracking.forEach(x => {


Test patch:
diff --git a/src/adapter/protocol/events.test.ts b/src/adapter/protocol/events.test.ts
index b2694c4..aa7948c 100644
--- a/src/adapter/protocol/events.test.ts
+++ b/src/adapter/protocol/events.test.ts
@@ -9,7 +9,7 @@ describe("applyEvent", () => {
 		const store = createStore();
 		const data = fromSnapshot(["rootId: 1"]);
 		applyEvent(store, "operation_v2", data);
-		expect(store.roots.$.length).to.equal(1);
+		expect(store.roots.value.length).to.equal(1);
 	});
 
 	it("should update roots correctly", () => {
@@ -20,7 +20,7 @@ describe("applyEvent", () => {
 			"Add 2 <div> to parent 1",
 		]);
 		applyEvent(store, "operation_v2", data);
-		expect(store.roots.$.length).to.equal(1);
+		expect(store.roots.value.length).to.equal(1);
 	});
 
 	it("should mount nodes", () => {
@@ -31,10 +31,10 @@ describe("applyEvent", () => {
 			"Add 2 <Parent> to parent 1",
 		]);
 		applyEvent(store, "operation_v2", data);
-		expect(store.nodes.$.size).to.equal(2);
-		expect(store.nodes.$.get(1)!.name).to.equal("Fragment");
-		expect(store.nodes.$.get(1)!.children).to.deep.equal([2]);
-		expect(store.nodes.$.get(2)!.name).to.equal("Parent");
+		expect(store.nodes.value.size).to.equal(2);
+		expect(store.nodes.value.get(1)!.name).to.equal("Fragment");
+		expect(store.nodes.value.get(1)!.children).to.deep.equal([2]);
+		expect(store.nodes.value.get(2)!.name).to.equal("Parent");
 	});
 
 	it("should do nothing on legacy update timings", () => {
@@ -45,7 +45,7 @@ describe("applyEvent", () => {
 			"Add 2 <Parent> to parent 1",
 		]);
 		applyEvent(store, "operation_v2", data);
-		expect(store.nodes.$.size).to.equal(2);
+		expect(store.nodes.value.size).to.equal(2);
 
 		const data2 = fromSnapshot([
 			"rootId: 1",
@@ -64,7 +64,7 @@ describe("applyEvent", () => {
 			"Add 2 <Parent> to parent 1",
 		]);
 		applyEvent(store, "operation_v2", data);
-		expect(store.nodes.$.size).to.equal(2);
+		expect(store.nodes.value.size).to.equal(2);
 
 		const data2 = fromSnapshot([
 			"rootId: 1",
@@ -74,10 +74,10 @@ describe("applyEvent", () => {
 
 		applyEvent(store, "operation_v2", data2);
 
-		expect(store.nodes.$.get(1)!.startTime).to.equal(2);
-		expect(store.nodes.$.get(1)!.endTime).to.equal(5);
-		expect(store.nodes.$.get(2)!.startTime).to.equal(3);
-		expect(store.nodes.$.get(2)!.endTime).to.equal(4);
+		expect(store.nodes.value.get(1)!.startTime).to.equal(2);
+		expect(store.nodes.value.get(1)!.endTime).to.equal(5);
+		expect(store.nodes.value.get(2)!.startTime).to.equal(3);
+		expect(store.nodes.value.get(2)!.endTime).to.equal(4);
 	});
 
 	it("should remove nodes", () => {
@@ -89,7 +89,7 @@ describe("applyEvent", () => {
 			"Add 3 <Foo> to parent 2",
 		]);
 		applyEvent(store, "operation_v2", data);
-		expect(store.nodes.$.size).to.equal(3);
+		expect(store.nodes.value.size).to.equal(3);
 
 		const data2 = fromSnapshot([
 			"rootId: 2",
@@ -97,8 +97,8 @@ describe("applyEvent", () => {
 			"Remove 3",
 		]);
 		applyEvent(store, "operation_v2", data2);
-		expect(store.nodes.$.size).to.equal(2);
-		expect(store.nodes.$.get(2)!.children).to.deep.equal([]);
+		expect(store.nodes.value.size).to.equal(2);
+		expect(store.nodes.value.get(2)!.children).to.deep.equal([]);
 	});
 
 	it("should remove nodes in any order", () => {
@@ -110,12 +110,12 @@ describe("applyEvent", () => {
 			"Add 3 <Foo> to parent 2",
 		]);
 		applyEvent(store, "operation_v2", data);
-		expect(store.nodes.$.size).to.equal(3);
+		expect(store.nodes.value.size).to.equal(3);
 
 		const data2 = fromSnapshot(["rootId: 1", "Remove 2", "Remove 3"]);
 		applyEvent(store, "operation_v2", data2);
-		expect(store.nodes.$.size).to.equal(1);
-		expect(store.nodes.$.get(1)!.children).to.deep.equal([]);
+		expect(store.nodes.value.size).to.equal(1);
+		expect(store.nodes.value.get(1)!.children).to.deep.equal([]);
 	});
 
 	it("should not throw on removing non-existing node", () => {
@@ -139,7 +139,7 @@ describe("applyEvent", () => {
 
 		const data2 = fromSnapshot(["rootId: 1", "Reorder 1 [3, 2]"]);
 		applyEvent(store, "operation_v2", data2);
-		expect(store.nodes.$.get(1)!.children).to.deep.equal([3, 2]);
+		expect(store.nodes.value.get(1)!.children).to.deep.equal([3, 2]);
 	});
 
 	it("should reorder children #2", () => {
@@ -156,7 +156,7 @@ describe("applyEvent", () => {
 
 		const data2 = fromSnapshot(["rootId: 1", "Reorder 1 [4, 3, 2, 5]"]);
 		applyEvent(store, "operation_v2", data2);
-		expect(store.nodes.$.get(1)!.children).to.deep.equal([4, 3, 2, 5]);
+		expect(store.nodes.value.get(1)!.children).to.deep.equal([4, 3, 2, 5]);
 	});
 
 	it("should apply after filter", () => {
@@ -212,8 +212,8 @@ describe("applyEvent", () => {
 		]);
 		applyEvent(store, "operation_v2", data3);
 
-		expect(store.nodes.$.has(1)).to.be.true;
-		expect(store.nodes.$.get(1)!.children).to.deep.equal([17]);
+		expect(store.nodes.value.has(1)).to.be.true;
+		expect(store.nodes.value.get(1)!.children).to.deep.equal([17]);
 	});
 
 	it("should update inspect data when inspected node is updated", () => {
@@ -221,7 +221,7 @@ describe("applyEvent", () => {
 		const store = createStore();
 		store.subscribe(spy);
 
-		store.inspectData.$ = {
+		store.inspectData.value = {
 			id: 2,
 			key: null,
 			context: null,
@@ -248,7 +248,7 @@ describe("applyEvent", () => {
 	it("should reset uncollapsed state", () => {
 		const store = createStore();
 
-		store.inspectData.$ = {
+		store.inspectData.value = {
 			id: 2,
 			key: null,
 			context: null,
@@ -261,9 +261,9 @@ describe("applyEvent", () => {
 			suspended: false,
 		};
 
-		store.sidebar.props.uncollapsed.$ = ["a", "b", "c"];
-		store.sidebar.state.uncollapsed.$ = ["a", "b", "c"];
-		store.sidebar.context.uncollapsed.$ = ["a", "b", "c"];
+		store.sidebar.props.uncollapsed.value = ["a", "b", "c"];
+		store.sidebar.state.uncollapsed.value = ["a", "b", "c"];
+		store.sidebar.context.uncollapsed.value = ["a", "b", "c"];
 
 		applyEvent(store, "inspect-result", {
 			id: 42,
@@ -276,8 +276,8 @@ describe("applyEvent", () => {
 			state: null,
 		});
 
-		expect(store.sidebar.props.uncollapsed.$).to.deep.equal([]);
-		expect(store.sidebar.state.uncollapsed.$).to.deep.equal([]);
-		expect(store.sidebar.context.uncollapsed.$).to.deep.equal([]);
+		expect(store.sidebar.props.uncollapsed.value).to.deep.equal([]);
+		expect(store.sidebar.state.uncollapsed.value).to.deep.equal([]);
+		expect(store.sidebar.context.uncollapsed.value).to.deep.equal([]);
 	});
 });
diff --git a/src/adapter/store.test.ts b/src/adapter/store.test.ts
index 3fec2f8..d4f4c97 100644
--- a/src/adapter/store.test.ts
+++ b/src/adapter/store.test.ts
@@ -18,9 +18,9 @@ describe("Store", () => {
 		]);
 		applyOperationsV2(store, event);
 
-		expect(store.nodes.$.get(1)!.children).to.deep.equal([2]);
-		expect(store.nodes.$.get(2)!.children).to.deep.equal([3, 5]);
-		expect(store.nodes.$.get(3)!.children).to.deep.equal([4]);
+		expect(store.nodes.value.get(1)!.children).to.deep.equal([2]);
+		expect(store.nodes.value.get(2)!.children).to.deep.equal([3, 5]);
+		expect(store.nodes.value.get(3)!.children).to.deep.equal([4]);
 	});
 
 	it("should update durations", () => {
@@ -39,8 +39,8 @@ describe("Store", () => {
     ]);
 		applyOperationsV2(store, event2);
 
-		expect(store.nodes.$.get(1)!.startTime).to.equal(12);
-		expect(store.nodes.$.get(1)!.endTime).to.equal(15);
+		expect(store.nodes.value.get(1)!.startTime).to.equal(12);
+		expect(store.nodes.value.get(1)!.endTime).to.equal(15);
 	});
 
 	it("should unmount vnodes", () => {
@@ -65,8 +65,8 @@ describe("Store", () => {
 		applyOperationsV2(store, event2);
 
 		expect(spy.callCount).to.eq(2); // TODO: Should be called once
-		expect(store.nodes.$.get(1)!.children).to.deep.equal([]);
-		expect(store.nodes.$.get(2)).to.equal(undefined);
-		expect(store.nodes.$.get(3)).to.equal(undefined);
+		expect(store.nodes.value.get(1)!.children).to.deep.equal([]);
+		expect(store.nodes.value.get(2)).to.equal(undefined);
+		expect(store.nodes.value.get(3)).to.equal(undefined);
 	});
 });
diff --git a/src/view/components/elements/TreeView.test.tsx b/src/view/components/elements/TreeView.test.tsx
index fdc74df..785950c 100644
--- a/src/view/components/elements/TreeView.test.tsx
+++ b/src/view/components/elements/TreeView.test.tsx
@@ -9,7 +9,7 @@ import { DevNodeType } from "../../store/types";
 describe("TreeItem", () => {
 	it("should limit key length to 15", () => {
 		const store = createStore();
-		store.nodes.$.set(1, {
+		store.nodes.value.set(1, {
 			children: [],
 			depth: 1,
 			endTime: 0,
@@ -29,7 +29,7 @@ describe("TreeItem", () => {
 		);
 		expect(container.textContent).to.equal('foo key="abcdefghijklmno…",');
 
-		store.nodes.$.get(1)!.key = "foobar";
+		store.nodes.value.get(1)!.key = "foobar";
 		rerender(
 			<AppCtx.Provider value={store}>
 				<TreeItem id={1} key="" top={0} />,
diff --git a/src/view/store/react-bindings.test.tsx b/src/view/store/react-bindings.test.tsx
index 439452d..de7e5e5 100644
--- a/src/view/store/react-bindings.test.tsx
+++ b/src/view/store/react-bindings.test.tsx
@@ -1,31 +1,31 @@
 import { h } from "preact";
 import { expect } from "chai";
 import { act } from "preact/test-utils";
-import { valoo } from "../valoo";
+import { signal } from "../valoo";
 import { useObserver } from "./react-bindings";
 import { renderTest } from "../components/profiler/components/CommitTimeline/CommitTimeline.test";
 
 describe("useObserver", () => {
 	it("should render on update", () => {
-		const v = valoo(0);
+		const v = signal(0);
 		const Foo = () => {
-			const x = useObserver(() => v.$);
+			const x = useObserver(() => v.value);
 			return <div>{x}</div>;
 		};
 		const { container } = renderTest(<Foo />);
 		expect(container.textContent).to.equal("0");
 
 		act(() => {
-			v.$ = 42;
+			v.value = 42;
 		});
 
 		expect(container.textContent).to.equal("42");
 	});
 
 	it("should render on complex value update", () => {
-		const v = valoo([1]);
+		const v = signal([1]);
 		const Foo = () => {
-			const x = useObserver(() => v.$);
+			const x = useObserver(() => v.value);
 			return <div>{x.join(",")}</div>;
 		};
 		const { container } = renderTest(<Foo />);
diff --git a/src/view/valoo.test.ts b/src/view/valoo.test.ts
index 6166d36..ca30406 100644
--- a/src/view/valoo.test.ts
+++ b/src/view/valoo.test.ts
@@ -1,56 +1,56 @@
 import { expect } from "chai";
 import * as sinon from "sinon";
-import { valoo, watch } from "./valoo";
+import { signal, watch } from "./valoo";
 
 describe("valoo", () => {
 	describe("primitive", () => {
 		it("should read and write values", () => {
-			const a = valoo(2);
-			expect(a.$).to.eq(2);
+			const a = signal(2);
+			expect(a.value).to.eq(2);
 
-			a.$ = 3;
-			expect(a.$).to.eq(3);
+			a.value = 3;
+			expect(a.value).to.eq(3);
 		});
 
 		it("should update mutable values", () => {
-			const a = valoo([1]);
+			const a = signal([1]);
 			a.update(v => {
 				v.push(2);
 			});
-			expect(a.$).to.deep.eq([1, 2]);
+			expect(a.value).to.deep.eq([1, 2]);
 		});
 
 		it("should update mutable values without arguments to update", () => {
-			const a = valoo([1]);
+			const a = signal([1]);
 			const spy = sinon.spy();
 			a.on(spy);
 
-			a.$.push(2);
+			a.value.push(2);
 			a.update();
 
 			expect(spy.callCount).to.eq(1);
 			expect(spy.args[0][0].length).to.eq(2);
-			expect(a.$).to.deep.eq([1, 2]);
+			expect(a.value).to.deep.eq([1, 2]);
 		});
 
 		it("should call listener", () => {
-			const a = valoo(1);
+			const a = signal(1);
 			const spy = sinon.spy();
 			a.on(spy);
 
-			a.$ = 2;
+			a.value = 2;
 			expect(spy.callCount).to.eq(1);
 			expect(spy.args[0]).to.deep.eq([2]);
 		});
 
 		it("should dispose listener", () => {
-			const a = valoo(1);
+			const a = signal(1);
 			const spy = sinon.spy();
 			const disp = a.on(spy);
 
-			a.$ = 2;
+			a.value = 2;
 			disp();
-			a.$ = 3;
+			a.value = 3;
 
 			expect(spy.callCount).to.eq(1);
 		});
@@ -58,47 +58,47 @@ describe("valoo", () => {
 
 	describe("watch", () => {
 		it("should subscribe to dependencies", () => {
-			const a = valoo(1);
-			const b = valoo(2);
+			const a = signal(1);
+			const b = signal(2);
 			const spy = sinon.spy();
 
-			const c = watch(() => a.$ + b.$);
+			const c = watch(() => a.value + b.value);
 			c.on(spy);
 
-			expect(c.$).to.eq(3);
+			expect(c.value).to.eq(3);
 
-			a.$ = 2;
+			a.value = 2;
 			expect(spy.callCount).to.eq(1);
-			expect(c.$).to.eq(4);
+			expect(c.value).to.eq(4);
 
-			b.$ = 3;
+			b.value = 3;
 			expect(spy.callCount).to.eq(2);
-			expect(c.$).to.eq(5);
+			expect(c.value).to.eq(5);
 		});
 
 		it("should unsubscribe from dependencies", () => {
-			const a = valoo(1);
-			const b = valoo(2);
+			const a = signal(1);
+			const b = signal(2);
 			const spy = sinon.spy();
 
-			const c = watch(() => (a.$ > 2 ? a.$ : b.$));
+			const c = watch(() => (a.value > 2 ? a.value : b.value));
 			c.on(spy);
 
-			a.$ = 3;
+			a.value = 3;
 			expect(spy.callCount).to.eq(1);
 
-			b.$ = 3;
+			b.value = 3;
 			expect(spy.callCount).to.eq(1);
 		});
 
 		it("should not track itself", () => {
-			const a = valoo<number[]>([]);
-			const b = valoo(1);
+			const a = signal<number[]>([]);
+			const b = signal(1);
 			const spy = sinon.spy();
 
 			const c = watch(() => {
 				a.update(v => {
-					v.push(b.$);
+					v.push(b.value);
 				});
 			});
 			c.on(spy);
@@ -110,12 +110,12 @@ describe("valoo", () => {
 		});
 
 		it("should not watch on callback", () => {
-			const a = valoo<number[]>([]);
-			const b = valoo(1);
+			const a = signal<number[]>([]);
+			const b = signal(1);
 			const spy = sinon.spy();
 
 			a.on(() => {
-				b.$ = a.$.length + 1;
+				b.value = a.value.length + 1;
 			});
 
 			watch(() => {