diff --git a/Editor.meta b/Editor.meta
new file mode 100644
index 0000000..8976890
--- /dev/null
+++ b/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7d89f89053ce0384c9f7b48a5b491bca
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/EncounterBrowserWindow.cs b/Editor/EncounterBrowserWindow.cs
new file mode 100644
index 0000000..9deb9b9
--- /dev/null
+++ b/Editor/EncounterBrowserWindow.cs
@@ -0,0 +1,341 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Jovian.EncounterSystem;
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Jovian.EncounterSystem.Editor {
+ ///
+ /// Designer-facing browser for every authored across all
+ /// assets in the project. Virtualised ListView on the left,
+ /// property-field detail pane on the right. Search by id/name/description; filter by kind.
+ ///
+ public class EncounterBrowserWindow : EditorWindow {
+ private const string AllKinds = "All";
+
+ private class Record {
+ public EncounterTable table;
+ public int index;
+ public IEncounter encounter;
+ }
+
+ private readonly List allRecords = new();
+ private List filteredRecords = new();
+ private string searchText = string.Empty;
+ private string kindFilter = AllKinds;
+
+ private readonly Dictionary> issuesByEncounter = new();
+
+ private ListView listView;
+ private VisualElement detailPane;
+ private ToolbarMenu kindDropdown;
+
+ [MenuItem("Jovian/Encounters/Encounter Browser")]
+ public static void Open() {
+ var window = GetWindow("Encounters");
+ window.minSize = new Vector2(640, 360);
+ }
+
+ private void CreateGUI() {
+ BuildToolbar();
+ BuildSplit();
+ Refresh();
+ }
+
+ private void BuildToolbar() {
+ var toolbar = new Toolbar();
+
+ var search = new ToolbarSearchField();
+ search.style.flexGrow = 1f;
+ search.RegisterValueChangedCallback(evt => {
+ searchText = evt.newValue ?? string.Empty;
+ ApplyFilter();
+ });
+ toolbar.Add(search);
+
+ kindDropdown = new ToolbarMenu { text = $"Kind: {AllKinds}" };
+ foreach(var choice in GetKindChoices()) {
+ var captured = choice;
+ kindDropdown.menu.AppendAction(captured, _ => {
+ kindFilter = captured;
+ kindDropdown.text = $"Kind: {captured}";
+ ApplyFilter();
+ });
+ }
+ toolbar.Add(kindDropdown);
+
+ var refreshButton = new ToolbarButton(Refresh) { text = "Refresh" };
+ toolbar.Add(refreshButton);
+
+ rootVisualElement.Add(toolbar);
+ }
+
+ private void BuildSplit() {
+ var split = new TwoPaneSplitView(0, 280, TwoPaneSplitViewOrientation.Horizontal);
+ split.style.flexGrow = 1f;
+ rootVisualElement.Add(split);
+
+ listView = new ListView {
+ makeItem = MakeRow,
+ bindItem = BindRow,
+ fixedItemHeight = 22,
+ selectionType = SelectionType.Single
+ };
+ listView.selectionChanged += OnSelectionChanged;
+ listView.style.flexGrow = 1f;
+ split.Add(listView);
+
+ detailPane = new ScrollView(ScrollViewMode.Vertical) {
+ style = { paddingLeft = 8, paddingTop = 8, paddingRight = 8, flexGrow = 1f }
+ };
+ ShowEmptyDetail();
+ split.Add(detailPane);
+ }
+
+ private static VisualElement MakeRow() {
+ var row = new VisualElement {
+ style = {
+ flexDirection = FlexDirection.Row,
+ alignItems = Align.Center,
+ paddingLeft = 6,
+ paddingRight = 6,
+ height = 22
+ }
+ };
+
+ var badge = new VisualElement {
+ name = "issue-badge",
+ style = {
+ width = 8,
+ height = 8,
+ marginRight = 6,
+ borderTopLeftRadius = 4,
+ borderTopRightRadius = 4,
+ borderBottomLeftRadius = 4,
+ borderBottomRightRadius = 4,
+ visibility = Visibility.Hidden
+ }
+ };
+ row.Add(badge);
+
+ var label = new Label {
+ name = "row-label",
+ style = {
+ flexGrow = 1f,
+ unityTextAlign = TextAnchor.MiddleLeft
+ }
+ };
+ row.Add(label);
+
+ return row;
+ }
+
+ private void BindRow(VisualElement element, int index) {
+ var record = filteredRecords[index];
+ var label = element.Q