Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1e9723f
Third Party Input
wallstop Nov 2, 2025
4307467
Merge remote-tracking branch 'origin/dev/wallstop/reddit-contract' in…
claude Nov 5, 2025
ed5a6f7
Fix async loading issues and _filteredObjects synchronization
claude Nov 5, 2025
6d39ac5
Fix CreateInstance errors and white screen on startup
claude Nov 6, 2025
d644c2d
Remove expensive CreateInstance validation for instant loading
claude Nov 6, 2025
780de32
Defer view building to eliminate white screen on startup
claude Nov 6, 2025
67f9122
Fix auto-scroll to selected element during async loading
claude Nov 6, 2025
eb6c760
Prioritize loading saved object to prevent fallback to first item
claude Nov 6, 2025
e712274
Fix filter logic to handle async loading and prevent ScrollTo crashes
claude Nov 6, 2025
08c1946
Delete TESTING_GUIDE.md
wallstop Nov 6, 2025
128c047
Delete TESTING_GUIDE.md.meta
wallstop Nov 6, 2025
6afb6fa
Fix saved object not being selected when in custom order position >100
claude Nov 6, 2025
0eb9684
Minimum widths
wallstop Nov 14, 2025
e9d133e
Better styling
wallstop Nov 14, 2025
bf0dd7f
Minimum theme
wallstop Nov 14, 2025
3955f95
Add PLAN
wallstop Nov 14, 2025
fd3c246
Update gitignore
wallstop Jul 6, 2026
a36e0b6
Merge main (EntityId migration #12/#13); untrack local planning artif…
wallstop Jul 6, 2026
8ce7a2c
fix: guard using UnityEditor in Runtime BaseDataObject
wallstop Jul 6, 2026
93940d4
chore: bump to 0.0.36 and fix doubled https:// in package.json URLs
wallstop Jul 6, 2026
bc4b627
docs: add AI-assistance disclosure; refresh AGENTS.md testing note
wallstop Jul 6, 2026
b68d436
fix(loading): address Cursor Bugbot + Copilot review of async loading
wallstop Jul 6, 2026
e4bb7dc
fix(loading): address re-review — load-generation guard, refresh sele…
wallstop Jul 6, 2026
3cd9805
fix(loading): address re-review round 3 — empty-type selection + sear…
wallstop Jul 6, 2026
a80a84b
fix(loading): address re-review round 4 — single refresh load + O(n) …
wallstop Jul 6, 2026
d54cf17
fix(loading): address re-review round 5 — refresh regression, drag of…
wallstop Jul 6, 2026
3afbb99
fix(search): give the search cache the same load-generation guard as …
wallstop Jul 6, 2026
bfd03c3
feat(objects): virtualize the object list with a ListView
wallstop Jul 6, 2026
b04a438
refactor(objects): remove dead pre-virtualization code
wallstop Jul 6, 2026
07ce038
ci: modernize npm publishing to manual dispatch with provenance
wallstop Jul 6, 2026
c07f6ef
fix(objects): resolve async-load UI races on type switch
wallstop Jul 6, 2026
b112613
fix(objects): keep in-flight order maps consistent on mid-load reorder
wallstop Jul 6, 2026
35908a0
fix(objects): correct restore selection + drop redundant load sort
wallstop Jul 6, 2026
7ee76f4
fix(objects): register type before load on restore + skip empty-type …
wallstop Jul 7, 2026
3d1fdfd
refactor(objects): drop dead synchronous loader + fix search-navigate…
wallstop Jul 7, 2026
2623ccd
perf+fix(objects): mid-load create consistency, search UX, batch drai…
wallstop Jul 7, 2026
2ca5d7c
fix(objects): auto-select after first real batch; drop duplicate lookup
wallstop Jul 7, 2026
e20b95d
fix(objects): ignore stale saved GUID; refresh search on empty cache
wallstop Jul 7, 2026
5e60eab
fix(objects): robust saved-GUID guard + accurate loading total
wallstop Jul 7, 2026
dfc4ea4
fix(objects): consistent GUID compare + guard processor during load
wallstop Jul 7, 2026
400c60b
fix(search): (re)populate search cache on demand when stale
wallstop Jul 7, 2026
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
121 changes: 67 additions & 54 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -1,75 +1,88 @@
name: Publish to NPM

# Manual, on-demand publishing (mirrors the DxMessaging release route): dispatch this workflow
# to publish the current package.json version. The publish step is re-runnable (it skips a
# version already on the registry) and attaches build provenance.
on:
push:
branches:
- main
paths:
- 'package.json'
workflow_dispatch:
inputs:
dry_run:
description: "Pack and report the version/dist-tag without publishing"
type: boolean
default: false

concurrency:
group: publish-npm
cancel-in-progress: false

permissions:
contents: read

jobs:
publish_npm:
publish:
name: Publish npm package
runs-on: ubuntu-latest

timeout-minutes: 15
permissions:
contents: read
id-token: write # required for `npm publish --provenance`
steps:
- name: Checkout Repository
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0 # Ensure full commit history for version comparison
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 18
registry-url: 'https://registry.npmjs.org/'
node-version: "22"
registry-url: "https://registry.npmjs.org"

- name: Check if version changed
id: version_check
- name: Pack and resolve version / dist-tag
id: pack
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "Manual trigger detected. Skipping version check."
NEW_VERSION=$(jq -r '.version' package.json)
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV
echo "should_publish=true" >> $GITHUB_ENV
if [[ "$NEW_VERSION" == *"rc"* ]]; then
echo "This is a pre-release (next tag)."
echo "NPM_TAG=next" >> $GITHUB_ENV
else
echo "This is a stable release (latest tag)."
echo "NPM_TAG=latest" >> $GITHUB_ENV
fi
set -euo pipefail
pkg="$(jq -r '.name' package.json)"
ver="$(jq -r '.version' package.json)"
if [ -z "${pkg}" ] || [ "${pkg}" = "null" ] || [ -z "${ver}" ] || [ "${ver}" = "null" ]; then
echo "::error::package.json is missing name or version."
exit 1
fi
# rc/alpha/beta/preview pre-releases publish under the "next" dist-tag; stable under "latest".
if printf '%s' "${ver}" | grep -qiE '\-(rc|alpha|beta|preview)'; then
npm_tag="next"
else
PREV_VERSION=$(git show HEAD~1:package.json | jq -r '.version' || echo "0.0.0")
NEW_VERSION=$(jq -r '.version' package.json)
echo "Previous version: $PREV_VERSION"
echo "New version: $NEW_VERSION"

if [ "$PREV_VERSION" != "$NEW_VERSION" ]; then
echo "Version changed, proceeding..."
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV
echo "should_publish=true" >> $GITHUB_ENV

# Detect pre-releases (versions with "rc" or similar tags)
if [[ "$NEW_VERSION" == *"rc"* ]]; then
echo "This is a pre-release (next tag)."
echo "NPM_TAG=next" >> $GITHUB_ENV
else
echo "This is a stable release (latest tag)."
echo "NPM_TAG=latest" >> $GITHUB_ENV
fi
else
echo "Version did not change, skipping..."
echo "should_publish=false" >> $GITHUB_ENV
fi
npm_tag="latest"
fi
package_file="$(npm pack --json | jq -r '.[0].filename')"
{
echo "pkg=${pkg}"
echo "ver=${ver}"
echo "npm_tag=${npm_tag}"
echo "package_file=${package_file}"
} >> "${GITHUB_OUTPUT}"
echo "Prepared ${pkg}@${ver} -> dist-tag '${npm_tag}' (${package_file})."

- name: Install Dependencies
if: env.should_publish == 'true'
run: npm install

- name: Publish to NPM
if: env.should_publish == 'true'
run: npm publish --access public --tag ${{ env.NPM_TAG }}
- name: Publish to npm with provenance
if: ${{ inputs.dry_run == false }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
PKG: ${{ steps.pack.outputs.pkg }}
VER: ${{ steps.pack.outputs.ver }}
NPM_TAG: ${{ steps.pack.outputs.npm_tag }}
PACKAGE_FILE: ${{ steps.pack.outputs.package_file }}
run: |
set -euo pipefail
# Re-runnable: skip if this exact name@version is already on the registry (a prior run may
# have published it before failing downstream). npm publish is otherwise irreversible.
if npm view "${PKG}@${VER}" version >/dev/null 2>&1; then
echo "::notice::${PKG}@${VER} is already on the registry; skipping publish."
else
npm publish "${PACKAGE_FILE}" --provenance --access public --tag "${NPM_TAG}"
echo "::notice::Published ${PKG}@${VER} to dist-tag '${NPM_TAG}'."
fi

- name: Dry-run summary
if: ${{ inputs.dry_run == true }}
run: |
echo "Dry run: would publish ${{ steps.pack.outputs.pkg }}@${{ steps.pack.outputs.ver }} to dist-tag '${{ steps.pack.outputs.npm_tag }}'."
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This .gitignore file should be placed at the root of your Unity project directory
# This .gitignore file should be placed at the root of your Unity project directory
#
# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore
#
Expand Down Expand Up @@ -72,3 +72,9 @@ crashlytics-build.properties
# Temporary auto-generated Android Assets
/[Aa]ssets/[Ss]treamingAssets/aa.meta
/[Aa]ssets/[Ss]treamingAssets/aa/*

.vscode/
.vscode.meta
progress/
progress.meta
PLAN.md*
19 changes: 19 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Repository Guidelines

## Project Structure & Module Organization
This Unity Package Manager module lives at `Packages/com.wallstop-studios.data-visualizer`. Keep runtime-facing APIs, ScriptableObject base classes, and shared attributes inside `Runtime/` so downstream games can include the package without editor baggage. Place editor windows, UI Toolkit layouts, and menu integrations in `Editor/`. Documentation assets (screens, GIFs, and the `README.md`) stay under `docs/`. EditMode tests live in `Tests/Editor` (`WallstopStudios.DataVisualizer.Tests.Editor.asmdef`); add PlayMode coverage in a sibling `Tests/Runtime` folder mirroring Unity’s layout.

## Build, Test, and Development Commands
- `unity -projectPath <path-to-host-project> -batchmode -quit -runTests -testPlatform editmode` runs EditMode coverage and surfaces compilation issues headlessly.
- `unity -projectPath <path-to-host-project> -batchmode -quit -runTests -testPlatform playmode` exercises runtime lifecycle hooks before promoting releases.
- `npm pack` (from this directory) generates the `.tgz` artifact consumed by scoped registries or `manifest.json` file references.
- `dotnet tool restore` installs the pinned .NET toolset, and `dotnet tool run csharpier -- format Editor Runtime` applies the CSharpier 1.1.2 style (use `-- check` in CI to fail fast).

## Coding Style & Naming Conventions
Target C# 10 with 4-space indentation, block-scoped namespaces (the existing style, with `using` directives inside the namespace), and analyzer warnings resolved before review. Follow Unity conventions: ScriptableObjects end in `Data`, `Settings`, or `Profile`, editor windows end in `Window`, and private serialized fields use camelCase names with `[SerializeField]`. Run the repo-pinned formatter (`dotnet tool run csharpier -- format <paths>`) on every modified file after `dotnet tool restore`; avoid manual line wrapping. Prefer explicit namespaces so the Data Visualizer window keeps its namespace/type tree predictable. Avoid runtime reflection and stringly-typed lookups, and depend on `nameof` expressions to wire menu items, property paths, and analytics IDs. Note that `InternalsVisibleTo` is not honored for the editor→tests assembly pair in Unity's compilation, so helpers that tests must exercise are `public` (e.g. `ObjectIdExtensions`, `LabelFilterEvaluator`).

## Testing Guidelines
Leverage Unity Test Framework. Group EditMode specs by feature (`NamespaceOrderingTests`, `SelectionPersistenceTests`) and name methods `Should_<Expectation>_When_<Condition>`. Add PlayMode tests for `BaseDataObject` lifecycle callbacks and asset-state persistence. Gate pull requests on both test suites using the commands above, and aim for coverage on ordering, filtering, and cloning paths before tagging a release.

## Commit & Pull Request Guidelines
Existing history favors short, imperative subject lines (e.g., “Fix saved object selection when ordering >100”) with the subsystem up front. Reference any related issue IDs in the body. Pull requests must include: summary of behavior change, reproduction/validation steps, screenshots or GIFs for UI tweaks, and a risk callout plus rollback plan. Confirm CSharpier formatting, `npm pack`, and both Unity test commands before requesting review.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 88 additions & 0 deletions Editor/DataVisualizer/Data/LabelFilterEvaluator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
namespace WallstopStudios.DataVisualizer.Editor.Data
{
using System;
using System.Collections.Generic;

/// <summary>
/// Pure evaluation of a <see cref="TypeLabelFilterConfig"/> against an object's Unity asset
/// labels. The AND/OR combination rules previously lived in two copies (the live filter loop and
/// a dead helper) which drifted apart; consolidating them here fixes that and lets the rules be
/// unit tested without an editor window or real assets.
///
/// Public because the package's editor test assembly cannot see internals of the editor
/// assembly (InternalsVisibleTo is not honored for that pair in Unity's compilation).
/// </summary>
public static class LabelFilterEvaluator
{
/// <summary>
/// Returns whether <paramref name="objectLabels"/> satisfy <paramref name="config"/>. An
/// empty AND or OR clause is treated as "no constraint". In OR mode an object must satisfy at
/// least one <em>active</em> clause; an empty clause never counts as a match — otherwise every
/// object would pass whenever only the other clause was populated (the historical bug where
/// OR-mode with only OR labels matched everything).
/// </summary>
public static bool Matches(IReadOnlyList<string> objectLabels, TypeLabelFilterConfig config)
{
if (config == null)
{
return true;
}

List<string> andLabels = config.andLabels;
List<string> orLabels = config.orLabels;
bool hasAnd = andLabels is { Count: > 0 };
bool hasOr = orLabels is { Count: > 0 };
if (!hasAnd && !hasOr)
{
return true;
}

IReadOnlyList<string> present = objectLabels ?? Array.Empty<string>();
bool andSatisfied = hasAnd && AllPresent(andLabels, present);
bool orSatisfied = hasOr && AnyPresent(orLabels, present);

return config.combinationType == LabelCombinationType.Or
? andSatisfied || orSatisfied // at least one ACTIVE clause must match
: (!hasAnd || andSatisfied) && (!hasOr || orSatisfied); // every active clause

// Local, non-capturing helpers keep this allocation-free: Matches runs once per object
// during filtering, so a per-call HashSet or closure would add real GC pressure. Asset
// label sets are tiny, so linear membership over the provided list is cheap.
static bool AllPresent(List<string> required, IReadOnlyList<string> labels)
{
for (int i = 0; i < required.Count; i++)
{
if (!Contains(labels, required[i]))
{
return false;
}
}
return true;
}

static bool AnyPresent(List<string> candidates, IReadOnlyList<string> labels)
{
for (int i = 0; i < candidates.Count; i++)
{
if (Contains(labels, candidates[i]))
{
return true;
}
}
return false;
}

static bool Contains(IReadOnlyList<string> labels, string target)
{
for (int i = 0; i < labels.Count; i++)
{
if (string.Equals(labels[i], target, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
}
}
}
2 changes: 2 additions & 0 deletions Editor/DataVisualizer/Data/LabelFilterEvaluator.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading