diff --git a/app/components/climb-card/climb-card.tsx b/app/components/climb-card/climb-card.tsx
index b3f495f..4eac980 100644
--- a/app/components/climb-card/climb-card.tsx
+++ b/app/components/climb-card/climb-card.tsx
@@ -2,11 +2,12 @@
import React from 'react';
import Card from 'antd/es/card';
-import { CopyrightOutlined } from '@ant-design/icons';
+import { CopyrightOutlined, StopOutlined } from '@ant-design/icons';
import ClimbCardCover from './climb-card-cover';
import { Climb, BoardDetails } from '@/app/lib/types';
import ClimbCardActions from './climb-card-actions';
+import { hasNoMatchingPattern } from '@/app/lib/climb-utils';
type ClimbCardProps = {
climb?: Climb;
@@ -26,6 +27,9 @@ const ClimbCard = ({ climb, boardDetails, onCoverClick, selected, actions }: Cli
{climb.name} @ {climb.angle}°
{climb.benchmark_difficulty !== null && }
+ {boardDetails.board_name === 'tension' && hasNoMatchingPattern(climb.description) && (
+
+ )}
{/* RIGHT: Difficulty, Quality */}
diff --git a/app/lib/__tests__/climb-utils.test.ts b/app/lib/__tests__/climb-utils.test.ts
new file mode 100644
index 0000000..60a5f24
--- /dev/null
+++ b/app/lib/__tests__/climb-utils.test.ts
@@ -0,0 +1,33 @@
+import { hasNoMatchingPattern } from '../climb-utils';
+
+describe('climb-utils', () => {
+ describe('hasNoMatchingPattern', () => {
+ it('should return true for "no matching" patterns', () => {
+ expect(hasNoMatchingPattern('No matching allowed')).toBe(true);
+ expect(hasNoMatchingPattern('Don\'t match this route')).toBe(true);
+ expect(hasNoMatchingPattern('Dont match on this problem')).toBe(true);
+ expect(hasNoMatchingPattern('no match')).toBe(true);
+ expect(hasNoMatchingPattern('don\'t matching')).toBe(true);
+ });
+
+ it('should return false for descriptions without no-matching patterns', () => {
+ expect(hasNoMatchingPattern('Great route with good holds')).toBe(false);
+ expect(hasNoMatchingPattern('Matching holds on this climb')).toBe(false);
+ expect(hasNoMatchingPattern('Really fun project')).toBe(false);
+ expect(hasNoMatchingPattern('')).toBe(false);
+ });
+
+ it('should be case insensitive', () => {
+ expect(hasNoMatchingPattern('NO MATCHING')).toBe(true);
+ expect(hasNoMatchingPattern('Don\'T Match')).toBe(true);
+ expect(hasNoMatchingPattern('No Match')).toBe(true);
+ });
+
+ it('should handle whitespace variations', () => {
+ expect(hasNoMatchingPattern('no match')).toBe(true);
+ expect(hasNoMatchingPattern('no match')).toBe(true);
+ expect(hasNoMatchingPattern('don\'tmatch')).toBe(true);
+ expect(hasNoMatchingPattern('don\'t match')).toBe(true);
+ });
+ });
+});
\ No newline at end of file
diff --git a/app/lib/climb-utils.ts b/app/lib/climb-utils.ts
new file mode 100644
index 0000000..ae4499a
--- /dev/null
+++ b/app/lib/climb-utils.ts
@@ -0,0 +1,16 @@
+/**
+ * Utility functions for climb-related operations
+ */
+
+/**
+ * Check if a climb description contains "no matching" text
+ * Uses regex pattern: \b(?:no|don'?t)\s?match(?:ing)?\b
+ * @param description - The climb description to check
+ * @returns true if the description contains "no matching" pattern
+ */
+export function hasNoMatchingPattern(description: string): boolean {
+ if (!description) return false;
+
+ const noMatchingRegex = /\b(?:no|don'?t)\s*match(?:ing)?\b/i;
+ return noMatchingRegex.test(description);
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index a682e9d..f5d90c5 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -25,5 +25,5 @@
"target": "ES2017"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
+ "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx", "**/__tests__/**"]
}