task-tagging

Provides categorical organization for tasks using a predefined set of seven standard tags (Work, Personal, Shopping, Health, Finance, Learning, Urgent). Enforces tag validation rules including duplica

task
tagging

SKILL.md

GitHub

Task Tagging Skill

Overview

The task tagging skill enables contextual categorization of tasks using a standardized set of tags. This skill provides consistent visual styling, validation rules, and user interface behaviors for tag management.

When to Apply

Apply this skill:

  • When creating a new task and user adds tags
  • When editing an existing task's tags
  • During tag input and autocomplete interactions
  • When displaying task lists with tag filters
  • During bulk import or migration (to validate and apply tags)
  • When rendering tag chips in UI components

Standard Tag Categories

This skill defines exactly seven standard tag categories:

  1. Work - Professional tasks, meetings, projects
  2. Personal - Personal errands, appointments, life admin
  3. Shopping - Purchase items, grocery lists, shopping errands
  4. Health - Medical appointments, exercise, wellness activities
  5. Finance - Bills, budgeting, financial planning
  6. Learning - Study, courses, skill development
  7. Urgent - Time-sensitive tasks requiring immediate attention

Tag Rules and Constraints

Primary and Secondary Tags

  • One primary tag: The main category for the task
  • Up to 4 secondary tags: Additional contextual tags
  • Maximum 5 tags total per task

Example valid combinations:

// Valid: 1 primary + 2 secondary tags
{ tags: ['Work', 'Urgent', 'Finance'] }

// Valid: 1 primary tag only
{ tags: ['Personal'] }

// Valid: 1 primary + 4 secondary tags (max)
{ tags: ['Work', 'Learning', 'Finance', 'Urgent', 'Health'] }

// Invalid: 6 tags (exceeds maximum)
{ tags: ['Work', 'Personal', 'Shopping', 'Health', 'Finance', 'Learning'] }

Duplicate Prevention

No duplicate tags are allowed on the same task:

// Valid: All unique tags
{ tags: ['Work', 'Urgent'] }

// Invalid: 'Work' appears twice
{ tags: ['Work', 'Work', 'Personal'] }

Tag Validation Rules

  1. Maximum count: 5 tags per task
  2. No duplicates: Each tag must be unique
  3. Standard categories only: Tags must be from the predefined set
  4. Case handling: Tag names are case-sensitive and should match exactly

Tag Color Scheme

Each tag has a specific color for visual consistency:

Work Tag

.tag-work {
  background-color: #DBEAFE; /* Light blue background */
  color: #1E40AF; /* Dark blue text */
  border: 1px solid #93C5FD;
}
  • Background: #DBEAFE (light blue)
  • Text: #1E40AF (dark blue)
  • Border: #93C5FD (medium blue)

Personal Tag

.tag-personal {
  background-color: #FCE7F3; /* Light pink background */
  color: #BE185D; /* Dark pink text */
  border: 1px solid #F9A8D4;
}
  • Background: #FCE7F3 (light pink)
  • Text: #BE185D (dark pink)
  • Border: #F9A8D4 (medium pink)

Shopping Tag

.tag-shopping {
  background-color: #FEF3C7; /* Light yellow background */
  color: #92400E; /* Dark amber text */
  border: 1px solid #FCD34D;
}
  • Background: #FEF3C7 (light yellow)
  • Text: #92400E (dark amber)
  • Border: #FCD34D (medium yellow)

Health Tag

.tag-health {
  background-color: #D1FAE5; /* Light green background */
  color: #065F46; /* Dark green text */
  border: 1px solid #6EE7B7;
}
  • Background: #D1FAE5 (light green)
  • Text: #065F46 (dark green)
  • Border: #6EE7B7 (medium green)

Finance Tag

.tag-finance {
  background-color: #E0E7FF; /* Light indigo background */
  color: #3730A3; /* Dark indigo text */
  border: 1px solid #A5B4FC;
}
  • Background: #E0E7FF (light indigo)
  • Text: #3730A3 (dark indigo)
  • Border: #A5B4FC (medium indigo)

Learning Tag

.tag-learning {
  background-color: #FECACA; /* Light red background */
  color: #991B1B; /* Dark red text */
  border: 1px solid #FCA5A5;
}
  • Background: #FECACA (light red)
  • Text: #991B1B (dark red)
  • Border: #FCA5A5 (medium red)

Urgent Tag

.tag-urgent {
  background-color: #FEE2E2; /* Light rose background */
  color: #7F1D1D; /* Dark rose text */
  border: 1px solid #FECACA;
  font-weight: 600; /* Bold for emphasis */
}
  • Background: #FEE2E2 (light rose)
  • Text: #7F1D1D (dark rose)
  • Border: #FECACA (medium rose)
  • Font weight: 600 (bold)

UI Chip/Badge Styling

All tag chips follow a consistent design pattern:

.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 12px;
  border-radius: 16px; /* Rounded corners */
  font-size: 12px;
  font-weight: 500;
  transition: all 0.2s ease;
  cursor: pointer;
}

/* Hover state */
.tag-chip:hover {
  opacity: 0.8;
  transform: translateY(-1px);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Active state */
.tag-chip:active {
  transform: translateY(0);
  box-shadow: none;
}

/* Remove button (X) - hidden by default */
.tag-chip .remove-btn {
  opacity: 0;
  margin-left: 4px;
  font-size: 14px;
  cursor: pointer;
  transition: opacity 0.2s ease;
}

/* Show remove button on hover */
.tag-chip:hover .remove-btn {
  opacity: 1;
}

.tag-chip .remove-btn:hover {
  color: #DC2626; /* Red on hover */
}

Visual characteristics:

  • Border radius: 16px (fully rounded)
  • Padding: 4px 12px
  • Font size: 12px
  • Font weight: 500
  • Gap between text and X button: 4px
  • Subtle background colors with contrasting text
  • X button appears on hover
  • Hover lift effect (translateY -1px)
  • Smooth transitions (0.2s)

Tag Autocomplete Behavior

Input Field Interaction

When user types in tag input field:

  1. Debounce: Wait 150ms after last keystroke
  2. Filter: Show matching tags from standard categories
  3. Case-insensitive matching: "wor" matches "Work"
  4. Prefix matching: Match from start of tag name
  5. Limit results: Show maximum 5 suggestions

Autocomplete Dropdown

function TagAutocomplete({ inputValue, onSelect }) {
  const [suggestions, setSuggestions] = useState([]);

  const standardTags = [
    'Work', 'Personal', 'Shopping',
    'Health', 'Finance', 'Learning', 'Urgent'
  ];

  useEffect(() => {
    const timer = setTimeout(() => {
      if (inputValue.length > 0) {
        const filtered = standardTags.filter(tag =>
          tag.toLowerCase().startsWith(inputValue.toLowerCase())
        );
        setSuggestions(filtered.slice(0, 5));
      } else {
        setSuggestions([]);
      }
    }, 150);

    return () => clearTimeout(timer);
  }, [inputValue]);

  return (
    <div className="autocomplete-dropdown">
      {suggestions.map(tag => (
        <div
          key={tag}
          className="autocomplete-item"
          onClick={() => onSelect(tag)}
        >
          {tag}
        </div>
      ))}
    </div>
  );
}

Autocomplete Styling

.autocomplete-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: white;
  border: 1px solid #E5E7EB;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  margin-top: 4px;
  max-height: 200px;
  overflow-y: auto;
  z-index: 1000;
}

.autocomplete-item {
  padding: 8px 12px;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.autocomplete-item:hover {
  background-color: #F3F4F6;
}

.autocomplete-item:active {
  background-color: #E5E7EB;
}

Tag Removal Interaction

Remove Button Behavior

  1. Hidden by default: X button has opacity: 0
  2. Show on hover: Opacity transitions to 1 on chip hover
  3. Click to remove: Clicking X removes the tag
  4. Confirmation: Optional confirmation for primary tag removal
  5. Immediate update: UI updates instantly on removal

Remove Button Implementation

function TagChip({ tag, onRemove, isPrimary }) {
  const [showConfirm, setShowConfirm] = useState(false);

  const handleRemove = () => {
    if (isPrimary && !showConfirm) {
      setShowConfirm(true);
      setTimeout(() => setShowConfirm(false), 3000);
    } else {
      onRemove(tag);
      setShowConfirm(false);
    }
  };

  return (
    <div className={`tag-chip tag-${tag.toLowerCase()}`}>
      <span>{tag}</span>
      <button
        className="remove-btn"
        onClick={handleRemove}
        aria-label={`Remove ${tag} tag`}
      >
        ×
      </button>
      {showConfirm && (
        <span className="confirm-text">Click again to remove</span>
      )}
    </div>
  );
}

Validation Implementation

Tag Validation Function

function validateTags(tags) {
  const errors = [];
  const standardTags = [
    'Work', 'Personal', 'Shopping',
    'Health', 'Finance', 'Learning', 'Urgent'
  ];

  // Check maximum count
  if (tags.length > 5) {
    errors.push('Maximum 5 tags allowed per task');
  }

  // Check for duplicates
  const uniqueTags = new Set(tags);
  if (uniqueTags.size !== tags.length) {
    errors.push('Duplicate tags are not allowed');
  }

  // Check for invalid tags
  const invalidTags = tags.filter(tag => !standardTags.includes(tag));
  if (invalidTags.length > 0) {
    errors.push(`Invalid tags: ${invalidTags.join(', ')}`);
  }

  return {
    isValid: errors.length === 0,
    errors
  };
}

Validation Error Display

function TagInput({ tags, setTags }) {
  const [validationErrors, setValidationErrors] = useState([]);

  const handleAddTag = (newTag) => {
    const updatedTags = [...tags, newTag];
    const validation = validateTags(updatedTags);

    if (validation.isValid) {
      setTags(updatedTags);
      setValidationErrors([]);
    } else {
      setValidationErrors(validation.errors);
      // Show error for 3 seconds
      setTimeout(() => setValidationErrors([]), 3000);
    }
  };

  return (
    <div className="tag-input-container">
      {/* Tag input field and chips */}
      {validationErrors.length > 0 && (
        <div className="validation-errors">
          {validationErrors.map((error, idx) => (
            <div key={idx} className="error-message">
              {error}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Complete UI Component Example

function TaskTagManager({ task, onUpdateTags }) {
  const [tags, setTags] = useState(task.tags || []);
  const [inputValue, setInputValue] = useState('');
  const [showAutocomplete, setShowAutocomplete] = useState(false);

  const standardTags = [
    'Work', 'Personal', 'Shopping',
    'Health', 'Finance', 'Learning', 'Urgent'
  ];

  const getTagClassName = (tag) => {
    return `tag-chip tag-${tag.toLowerCase()}`;
  };

  const handleAddTag = (tag) => {
    const validation = validateTags([...tags, tag]);
    if (validation.isValid) {
      const updatedTags = [...tags, tag];
      setTags(updatedTags);
      onUpdateTags(updatedTags);
      setInputValue('');
      setShowAutocomplete(false);
    } else {
      alert(validation.errors.join('\n'));
    }
  };

  const handleRemoveTag = (tagToRemove) => {
    const updatedTags = tags.filter(tag => tag !== tagToRemove);
    setTags(updatedTags);
    onUpdateTags(updatedTags);
  };

  return (
    <div className="task-tag-manager">
      {/* Display existing tags */}
      <div className="tag-list">
        {tags.map((tag, index) => (
          <div key={tag} className={getTagClassName(tag)}>
            <span>{tag}</span>
            <button
              className="remove-btn"
              onClick={() => handleRemoveTag(tag)}
            >
              ×
            </button>
          </div>
        ))}
      </div>

      {/* Tag input */}
      {tags.length < 5 && (
        <div className="tag-input-wrapper">
          <input
            type="text"
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
            onFocus={() => setShowAutocomplete(true)}
            placeholder="Add tag..."
            className="tag-input"
          />
          {showAutocomplete && (
            <TagAutocomplete
              inputValue={inputValue}
              onSelect={handleAddTag}
            />
          )}
        </div>
      )}
    </div>
  );
}

Testing Examples

Test Case 1: Valid Tag Addition

const tags = ['Work', 'Urgent'];
const validation = validateTags([...tags, 'Finance']);
// Expected: { isValid: true, errors: [] }

Test Case 2: Maximum Tags Exceeded

const tags = ['Work', 'Personal', 'Shopping', 'Health', 'Finance'];
const validation = validateTags([...tags, 'Learning']);
// Expected: { isValid: false, errors: ['Maximum 5 tags allowed per task'] }

Test Case 3: Duplicate Tag

const tags = ['Work', 'Urgent'];
const validation = validateTags([...tags, 'Work']);
// Expected: { isValid: false, errors: ['Duplicate tags are not allowed'] }

Test Case 4: Invalid Tag

const tags = ['Work'];
const validation = validateTags([...tags, 'Custom']);
// Expected: { isValid: false, errors: ['Invalid tags: Custom'] }

Integration Points

This skill integrates with:

  • Priority Classification Skill: Tags can influence priority (e.g., "Urgent" tag)
  • Task Filter Skill: Enables filtering by specific tags
  • Task Search Skill: Tags are searchable fields
  • Task Organization Agent: Uses tagging for contextual organization

Performance Considerations

  • Tag validation should complete in < 1ms
  • Autocomplete debounce prevents excessive filtering (150ms)
  • Tag chip rendering optimized with CSS transitions
  • Maximum 5 tags prevents UI clutter and performance issues

Accessibility Considerations

  • Remove buttons have aria-label for screen readers
  • Tag chips are keyboard navigable
  • Color contrast ratios meet WCAG AA standards
  • Focus states are clearly visible