mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-28 20:22:45 +00:00
fix(app): give subagent rows room on compact and standard press feedback (#7364)
The subagent track rows were as dense on a phone as in the desktop popover, and the archive and detach actions were hand-rolled pressables that only recoloured the glyph, so a tap or hover never showed the rounded highlight every other icon action has. Rows now take the compact menu-row height and padding below the `md` breakpoint, decided once in the track row style; the desktop popover keeps its density. The actions are `ToolbarButton`, which owns the hitbox, the rounded highlight on hover and press, the compact size, and the tooltip. A repo oxlint rule, `paseo/no-raw-pressable`, reports `Pressable` and `Touchable*` imports outside `components/ui/` so the next tap target reaches for a primitive. Existing importers are on a burn-down list they can only leave. The rule is its own name rather than another `no-restricted-imports` entry so its override cannot interact with the existing burn-downs. Its test runs the real oxlint binary and is wired into the CI quality job.
This commit is contained in:
@@ -379,6 +379,7 @@ test("quality preserves commands and routes each domain independently after fail
|
||||
"npx --yes lockfile-lint --path package-lock.json --type npm --allowed-hosts npm --validate-https --validate-integrity",
|
||||
"npm audit signatures",
|
||||
"npm run lint",
|
||||
"node --test scripts/oxlint/paseo-plugin.test.mjs",
|
||||
],
|
||||
relay: ["npm run build:relay", "npm run test --workspace=@getpaseo/relay"],
|
||||
sdk: [
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Repo-specific oxlint rules. Each rule is its own name, so its burn-down list in .oxlintrc.json
|
||||
// is one override that cannot interact with the `no-restricted-imports` burn-downs.
|
||||
|
||||
const PRESS_PRIMITIVE_SOURCES = new Set(["react-native", "react-native-gesture-handler"]);
|
||||
const PRESS_PRIMITIVE_NAMES = new Set([
|
||||
"Pressable",
|
||||
"TouchableHighlight",
|
||||
"TouchableNativeFeedback",
|
||||
"TouchableOpacity",
|
||||
"TouchableWithoutFeedback",
|
||||
]);
|
||||
|
||||
export const NO_RAW_PRESSABLE_MESSAGE =
|
||||
"A raw pressable has no standard press or hover feedback. Icon actions use ToolbarButton " +
|
||||
"(@/components/ui/pane-content-toolbar); rows on a menu surface use MenuItem or " +
|
||||
"ComposerTrackRow; other rows use PressHighlight (@/components/ui/press-highlight); labelled " +
|
||||
"buttons use Button. See docs/design.md §2.";
|
||||
|
||||
const noRawPressable = {
|
||||
create(context) {
|
||||
return {
|
||||
ImportDeclaration(node) {
|
||||
if (!PRESS_PRIMITIVE_SOURCES.has(node.source.value)) return;
|
||||
for (const specifier of node.specifiers) {
|
||||
if (
|
||||
specifier.type === "ImportSpecifier" &&
|
||||
PRESS_PRIMITIVE_NAMES.has(specifier.imported.name)
|
||||
) {
|
||||
context.report({ node: specifier, message: NO_RAW_PRESSABLE_MESSAGE });
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
meta: { name: "paseo" },
|
||||
rules: {
|
||||
"no-raw-pressable": noRawPressable,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { test } from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const oxlintBin = path.join(repoRoot, "node_modules/.bin/oxlint");
|
||||
const pluginPath = path.join(repoRoot, "scripts/oxlint/paseo-plugin.mjs");
|
||||
|
||||
function lint(source) {
|
||||
const dir = mkdtempSync(path.join(tmpdir(), "paseo-oxlint-"));
|
||||
try {
|
||||
writeFileSync(
|
||||
path.join(dir, ".oxlintrc.json"),
|
||||
JSON.stringify({ jsPlugins: [pluginPath], rules: { "paseo/no-raw-pressable": "error" } }),
|
||||
);
|
||||
writeFileSync(path.join(dir, "probe.tsx"), source);
|
||||
const result = spawnSync(oxlintBin, ["-c", ".oxlintrc.json", "probe.tsx"], {
|
||||
cwd: dir,
|
||||
encoding: "utf8",
|
||||
});
|
||||
return { failed: result.status !== 0, output: result.stdout + result.stderr };
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
test("a raw Pressable from react-native is reported", () => {
|
||||
const result = lint(
|
||||
'import { Pressable, View } from "react-native";\nexport { Pressable, View };\n',
|
||||
);
|
||||
assert.equal(result.failed, true);
|
||||
assert.match(result.output, /no-raw-pressable/);
|
||||
});
|
||||
|
||||
test("an aliased gesture-handler touchable is reported", () => {
|
||||
const result = lint(
|
||||
'import { TouchableOpacity as Tap } from "react-native-gesture-handler";\nexport { Tap };\n',
|
||||
);
|
||||
assert.equal(result.failed, true);
|
||||
assert.match(result.output, /no-raw-pressable/);
|
||||
});
|
||||
|
||||
test("press primitives and pressable types are allowed", () => {
|
||||
const result = lint(
|
||||
'import { View, type PressableProps } from "react-native";\n' +
|
||||
'import { ToolbarButton } from "@/components/ui/pane-content-toolbar";\n' +
|
||||
"export type Props = PressableProps;\nexport { View, ToolbarButton };\n",
|
||||
);
|
||||
assert.equal(result.failed, false, result.output);
|
||||
});
|
||||
Reference in New Issue
Block a user