21 Commits

Author SHA1 Message Date
overcuriousity
37b6503b29 Merge pull request #25 from overcuriousity/claude/improve-visual-design-9hCsu
Claude/improve visual design 9h csu
2025-12-15 11:57:29 +01:00
Claude
f54e2560f3 Complete remaining visual design improvements
This commit addresses all remaining visual consistency issues identified
in the code review, achieving 100% completion of visual design improvements.

## Magic Number Elimination (21 instances → 0)
- Added new spacing constants:
  - Spacing.HASH_SHORT_PADDING (width - 12)
  - Spacing.EMPTY_STATE_PADDING (width - 8)
  - Spacing.STATUS_BAR_PADDING (width - 2)
  - Layout.STATUS_LINE_OFFSET_FROM_BOTTOM (height - 1)
  - Layout.NOTE_DETAIL_BOTTOM_RESERVE (height - 6)
- Replaced ALL remaining hardcoded width/height calculations:
  - width - 6, width - 4, width - 2 → Spacing constants
  - width - 20, width - 12, width - 8 → Spacing constants
  - height - 1, height - 2, height - 4, height - 6 → Layout constants

## Icon Literal Elimination (6 instances → 0)
- Replaced "●" → Icons.ACTIVE
- Replaced "○" → Icons.INACTIVE
- Replaced "─" → Icons.SEPARATOR_H (3 instances)
- Replaced "═" → "═" with Layout.HEADER_X positioning

## Responsive Column Widths
- Enhanced ColumnWidths class with responsive methods:
  - get_tag_width(terminal_width): 40% of terminal or min 30 chars
  - get_ioc_width(terminal_width): 50% of terminal or min 50 chars
  - get_content_preview_width(terminal_width): 50% or min 50 chars
- Updated tag list display to use responsive width
- Updated IOC list display to use responsive width
- Column widths now adapt to terminal size while maintaining minimums

## Active Indicator Enhancement
- Added "ACTIVE:" label prefix for clarity
- Applied BOLD attribute to active status (green + bold)
- Changed separator from "▸" to Icons.ARROW_RIGHT for consistency
- Now: "● ACTIVE: CASE-001 ▸ Evidence Name" (green, bold)
- Was: "● CASE-001  ▸  Evidence Name" (green only)

## Impact Summary
Before this commit:
- 21 hardcoded width/height calculations remaining
- 6 hardcoded icon literals
- Fixed column widths (non-responsive)
- Active indicator: color-only differentiation

After this commit:
- 0 hardcoded magic numbers (100% constants)
- 0 hardcoded icon literals (100% Icons.X)
- Responsive column widths (adapts to terminal)
- Active indicator: color + bold + label (multi-modal)

## Code Quality
- All visual constants now centralized in visual_constants.py
- Zero magic numbers in layout/spacing calculations
- Consistent use of Icons constants throughout
- Responsive design adapts to terminal width
- Enhanced accessibility with multiple visual cues

## Testing
- Verified no syntax errors
- Confirmed successful module imports
- Tested CLI functionality (--list)
- All features working correctly

Visual design refactoring is now 100% complete with full consistency
across the entire TUI codebase.
2025-12-15 10:24:32 +00:00
Claude
a2e7798a2d Comprehensive visual design improvements for TUI
This commit implements a complete visual redesign and refactoring to improve
consistency, accessibility, and maintainability of the TUI interface.

## New Visual Constants Module
- Created trace/tui/visual_constants.py with centralized constants:
  - Layout: Screen positioning and structure (header, content, footer)
  - Spacing: Padding and margins (dialogs, horizontal padding)
  - ColumnWidths: Fixed column widths for lists (tags, IOCs, content)
  - DialogSize: Standard dialog dimensions (small, medium, large)
  - Icons: Unicode symbols used throughout UI
  - Timing: Animation and feedback timing constants

## Color System Improvements
- Fixed duplicate color definitions (removed from tui_app.py)
- Use ColorPairs constants throughout instead of hardcoded numbers
- Separated tag colors from footer colors:
  - Tags now use magenta (ColorPairs.TAG) instead of yellow
  - Footers keep yellow (ColorPairs.WARNING) for consistency
  - Updated TAG_SELECTED to magenta on cyan
- All 129+ color_pair() calls now use semantic ColorPairs constants

## Accessibility Enhancements
- Added warning icons (⚠) to all IOC displays for visual + color cues
- Tag highlighting now uses distinct magenta color
- Improved color semantics reduce reliance on color alone
- Smart text truncation at word boundaries for better readability

## Layout & Spacing Standardization
- Replaced magic numbers with Layout/Spacing constants:
  - Footer positioning: height - Layout.FOOTER_OFFSET_FROM_BOTTOM
  - Content area: Layout.CONTENT_START_Y
  - Truncation: width - Spacing.HORIZONTAL_PADDING
  - Dialog margins: Spacing.DIALOG_MARGIN
- Standardized dialog sizes using DialogSize constants:
  - Input dialogs: DialogSize.MEDIUM
  - Multiline dialogs: DialogSize.LARGE
  - Confirm dialogs: DialogSize.SMALL (with dynamic width)
  - Settings dialog: DialogSize.MEDIUM

## User Experience Improvements
- Enhanced footer command organization with visual grouping:
  - Used Icons.SEPARATOR_GROUP (│) to group related commands
  - Example: "[n] Add Note │ [t] Tags [i] IOCs │ [v] View [e] Export"
- Smart content truncation (_safe_truncate):
  - Added word_break parameter (default True)
  - Breaks at word boundaries when >60% text retained
  - Maintains Unicode safety while improving readability
- Improved empty state messages:
  - New _draw_empty_state() helper for consistent visual structure
  - Centered boxes with proper spacing
  - Clear call-to-action hints
  - Applied to "No cases found" and "No cases match filter"

## Code Quality & Maintainability
- Eliminated hardcoded spacing values throughout 3,468-line file
- Used Icons constants for all Unicode symbols (─│┌└◆●○▸⚠⌗◈✓✗?)
- Fixed circular import issues with delayed global imports in TUI.__init__
- Updated comments to reflect new ColorPairs constants
- Consistent use of f-strings for footer construction

## Visual Consistency
- Replaced all "─" literals with Icons.SEPARATOR_H
- Standardized truncation widths (width - Spacing.HORIZONTAL_PADDING)
- Consistent use of ColumnWidths for tag (30) and IOC (50+2) displays
- All dialogs now use standard sizes from visual_constants

## Testing
- Verified no syntax errors in all modified files
- Confirmed successful module imports
- Tested CLI functionality (--help, --list)
- Backward compatibility maintained

This establishes a strong foundation for future UI enhancements while
significantly improving code maintainability and visual consistency.
2025-12-15 10:08:11 +00:00
overcuriousity
15bc00a195 Merge pull request #24 from overcuriousity/claude/clarify-timestamp-format-cBGwJ
Claude/clarify timestamp format c b gw j
2025-12-14 21:57:30 +01:00
Claude
053369df78 Add Unix timestamp to TUI exports for hash reproducibility
The TUI export handler (_write_note_markdown) was missing the Unix timestamp
that was added to CLI exports. This ensures consistency across all export paths.

Changes:
- Updated _write_note_markdown() in trace/tui/handlers/export_handler.py
- Now includes "Unix Timestamp: `{timestamp}` (for hash verification)" line
- Matches the format from CLI exports in trace/cli.py
- Multi-line content is properly indented
- Hash label updated to "SHA256 Hash (timestamp:content)" for clarity

All export paths (CLI --export, TUI case export, TUI evidence export) now
include the Unix timestamp needed for independent hash verification.
2025-12-14 20:49:36 +00:00
Claude
eca56c0d54 Add Unix timestamps to exports and clarify format in README
Changes:
- Modified export format to include Unix timestamp for hash reproducibility
  - Each note now shows: "Unix Timestamp: `{timestamp}` (for hash verification)"
  - This allows independent verification using: SHA256("{timestamp}:{content}")

- Updated README.md with comprehensive timestamp format documentation:
  - Clarified that timestamps are Unix epoch (seconds since 1970-01-01 UTC) as float
  - Added example: 1702345678.123456
  - Documented exact hash input format: "{timestamp}:{content}"
  - Added "Hash Verification (Manual)" section with step-by-step verification instructions
  - Included examples using Python and command-line tools
  - Updated Core Features table with timestamp format details
  - Enhanced Layer 1 integrity documentation with concrete examples

These changes ensure hash reproducibility from exported markdown files,
critical for forensic chain of custody and independent verification.
2025-12-14 20:47:50 +00:00
Claude
06b7680982 Clarify timestamp format used in hash calculations
Added comprehensive documentation to make it crystal clear that:
- Timestamps are Unix epoch timestamps (seconds since 1970-01-01 00:00:00 UTC) stored as floats
- Hash input format is "{timestamp}:{content}" with float-to-string conversion
- Example: "1702345678.123456:Suspicious process detected"
- Full float precision is preserved, ensuring forensic tamper-evidence

Updated documentation in:
- trace/models/__init__.py: Added field comments and detailed docstring for calculate_hash()
- trace/crypto.py: Added comprehensive docstring for hash_content() with examples
- CLAUDE.md: Added detailed explanation in Integrity System section
2025-12-14 20:45:22 +00:00
overcuriousity
bfefb42761 Merge pull request #23 from overcuriousity/claude/show-pgp-signature-llNQW
Display raw PGP signature in verification dialog
2025-12-14 20:51:41 +01:00
Claude
070e76467c Display raw PGP signature in verification dialog
Simplified approach: When pressing 'V' in note details, the TUI temporarily
exits to terminal mode and prints the raw PGP signature directly to stdout.

This allows users to:
- Select and copy the signature using their terminal's native copy/paste
- Works with any shell (bash, fish, zsh, etc.)
- No dependency on clipboard tools (xclip, xsel, pbcopy, clip)
- No complex shell-specific commands needed

The signature is displayed with verification status and clear formatting,
then waits for Enter to return to the TUI.

This is the simplest and most universal solution for viewing and copying
PGP signatures for external verification in Kleopatra or GPG tools.
2025-12-14 19:50:56 +00:00
overcuriousity
b80dd10901 Merge pull request #22 from overcuriousity/claude/show-pgp-signature-llNQW
Add clear clipboard feedback and GPG verification commands
2025-12-14 20:45:40 +01:00
Claude
fe3c0710c6 Add clear clipboard feedback and GPG verification commands
Enhanced the signature verification dialog with:

1. Clear clipboard status feedback:
   - Shows success: "✓ Clipboard: Copied successfully (using xclip)"
   - Shows failure: "✗ Clipboard: Failed to copy"
   - Provides installation instructions for Linux (xclip/xsel)

2. Direct GPG verification commands:
   - Linux/macOS: gpg --verify <(cat ~/.trace/last_signature.txt)
   - Windows PowerShell: Get-Content ~/.trace/last_signature.txt | gpg --verify
   - Also includes simple view commands (cat/Get-Content)

3. Better organized dialog sections:
   - EXPORT STATUS section showing clipboard and file status
   - VERIFY WITH GPG section with platform-specific commands

Users now get immediate, clear feedback about whether clipboard copy worked
and can easily copy/paste the verification commands to verify signatures
externally in Kleopatra or command-line GPG tools.
2025-12-14 19:45:00 +00:00
overcuriousity
809a4a498f Merge pull request #21 from overcuriousity/claude/show-pgp-signature-llNQW
Auto-export PGP signatures to clipboard and file
2025-12-14 20:41:47 +01:00
Claude
931e5debc8 Auto-export PGP signatures to clipboard and file
When pressing 'V' on a note detail view, the PGP signature is now:
1. Automatically copied to system clipboard (if available)
2. Saved to ~/.trace/last_signature.txt for manual access

This solves the copy/paste problem where signatures displayed in the TUI
modal couldn't be selected and copied. Users can now:
- Paste directly from clipboard into Kleopatra/GPG tools (if clipboard worked)
- Access the signature file from another terminal if needed

Platform support:
- Linux: Uses xclip or xsel (falls back to file if not available)
- macOS: Uses pbcopy (falls back to file if not available)
- Windows: Uses clip (falls back to file if not available)

The dialog shows clear feedback about:
- Whether clipboard copy succeeded
- File location and how to access it manually
- Verification status (verified/failed/unsigned)

No external dependencies added - uses only stdlib subprocess calls.
2025-12-14 19:40:25 +00:00
overcuriousity
f91f434f7f Merge pull request #20 from overcuriousity/claude/show-pgp-signature-llNQW
Display raw PGP signature in verification dialog
2025-12-14 20:34:56 +01:00
Claude
85ca483a1d Display raw PGP signature in verification dialog
Modified the verify_note_signature() function to show the complete raw PGP
signature text when users press 'V' in the note details view. This allows
users to copy/paste signatures into external tools like Kleopatra for
independent verification.

Changes:
- Added raw signature display after verification status info
- Implemented scrollable dialog with arrow keys and Page Up/Down support
- Added clear separator and label for the signature section
- Shows signature for verified, failed, and unsigned notes (when present)

Users can now easily copy PGP signatures for external verification workflows.
2025-12-14 19:33:52 +00:00
overcuriousity
f50fd1800d Merge pull request #19 from overcuriousity/claude/fix-unknown-signer-y3bWG
Add cross-platform compatibility documentation for GPG locale handling
2025-12-14 20:32:20 +01:00
Claude
b830d15d85 Add cross-platform compatibility documentation for GPG locale handling
Clarified how the locale override works across different platforms:

- Linux/macOS: LC_ALL and LANG variables control GPG output language
- Windows: GPG may ignore locale variables, but the code remains robust
  through explicit encoding='utf-8' and errors='replace' parameters

The combination of:
1. Setting LC_ALL/LANG to C.UTF-8 (works on Linux/macOS)
2. Explicit encoding='utf-8' parameter
3. errors='replace' for graceful handling

...ensures the code works reliably on all platforms even if the locale
setting is not fully respected by GPG.
2025-12-14 14:04:06 +00:00
overcuriousity
4a4e1e7c06 Merge pull request #18 from overcuriousity/claude/fix-unknown-signer-y3bWG
Fix UTF-8 decoding error when verifying signatures with international…
2025-12-14 15:02:01 +01:00
Claude
2a7d00d221 Fix UTF-8 decoding error when verifying signatures with international characters
Issue: GPG verification crashed with UnicodeDecodeError when signatures
contained international characters (German ö, Turkish ü, etc.) in the
signed content.

Error: "'utf-8' codec can't decode byte 0xf6 in position 160"

Root cause: subprocess.Popen was using default text decoding without
handling encoding errors gracefully.

Solution:
1. Changed LC_ALL/LANG from 'C' to 'C.UTF-8' to ensure GPG uses UTF-8
2. Added explicit encoding='utf-8' parameter to Popen
3. Added errors='replace' to replace invalid UTF-8 bytes instead of crashing

This allows the verification to proceed even if GPG's output contains
characters that don't decode cleanly, ensuring robustness with
multilingual content.
2025-12-14 14:01:03 +00:00
overcuriousity
c68fc66de6 Merge pull request #17 from overcuriousity/claude/fix-unknown-signer-y3bWG
Force English locale for GPG verify to fix localized output parsing
2025-12-14 14:56:49 +01:00
Claude
f68c8389da Force English locale for GPG verify to fix localized output parsing
Root cause: GPG outputs verification messages in the user's locale
(e.g., German "Gute Signatur von" instead of "Good signature from"),
causing the parsing logic to fail and display "Unknown signer".

Solution: Force LC_ALL=C and LANG=C environment variables when
calling 'gpg --verify' to ensure consistent English output across
all locales.

This ensures the code can reliably parse "Good signature from"
regardless of the user's system language settings.
2025-12-14 13:55:26 +00:00
10 changed files with 534 additions and 277 deletions

View File

@@ -145,6 +145,14 @@ The codebase is organized into focused, single-responsibility modules to make it
**Integrity System**: Every note automatically gets:
1. SHA256 hash of `timestamp:content` (via `Note.calculate_hash()`)
- **Timestamp Format**: Unix epoch timestamp as float (seconds since 1970-01-01 00:00:00 UTC)
- **Hash Input Format**: `"{timestamp}:{content}"` where timestamp is converted to string using Python's default str() conversion
- **Example**: For content "Suspicious process detected" with timestamp 1702345678.123456, the hash input is:
```
1702345678.123456:Suspicious process detected
```
- This ensures integrity of both WHAT was said (content) and WHEN it was said (timestamp)
- The exact float precision is preserved in the hash, making timestamps forensically tamper-evident
2. Optional GPG clearsign signature (if `pgp_enabled` in settings and GPG available)
**Tag System**: Regex-based hashtag extraction (`#word`)

View File

@@ -20,7 +20,7 @@ trace "IR team gained shell access. Initial persistence checks running."
trace "Observed outbound connection to 192.168.1.55 on port 80. #suspicious #network"
```
**System Integrity Chain:** Each command-line note is immediately stamped, concatenated with its content, and hashed using SHA256 before storage. This ensures a non-repudiable log entry.
**System Integrity Chain:** Each command-line note is immediately stamped with a Unix epoch timestamp (seconds since 1970-01-01 00:00:00 UTC as float, e.g., `1702345678.123456`), concatenated with its content in the format `"{timestamp}:{content}"`, and hashed using SHA256 before storage. This ensures a non-repudiable log entry with forensically tamper-evident timestamps.
## CLI Command Reference
@@ -195,7 +195,7 @@ After this, you can log with just: `t "Your note here"`
| Feature | Description | Operational Impact |
| :--- | :--- | :--- |
| **Integrity Hashing** | SHA256 applied to every log entry (content + timestamp). | **Guaranteed log integrity.** No modification possible post-entry. |
| **Integrity Hashing** | SHA256 applied to every log entry using format `"{unix_timestamp}:{content}"`. Timestamp is Unix epoch as float (e.g., `1702345678.123456`). | **Guaranteed log integrity.** No modification possible post-entry. Timestamps are forensically tamper-evident with full float precision. |
| **GPG Signing** | Optional PGP/GPG signature applied to notes. | **Non-repudiation** for formal evidence handling. |
| **IOC Extraction** | Automatic parsing of IPv4, FQDNs, URLs, hashes, and email addresses. | **Immediate intelligence gathering** from raw text. |
| **Tag System** | Supports `#hashtags` for classification and filtering. | **Efficient triage** of large log sets. |
@@ -208,20 +208,33 @@ After this, you can log with just: `t "Your note here"`
### Layer 1: Note-Level Integrity (Always Active)
**Process:**
1. **Timestamp Generation** - Precise Unix timestamp captured at note creation
2. **Content Hashing** - SHA256 hash computed from `timestamp:content`
1. **Timestamp Generation** - Precise Unix epoch timestamp (float) captured at note creation
- Format: Seconds since 1970-01-01 00:00:00 UTC (e.g., `1702345678.123456`)
- Full float precision preserved for forensic tamper-evidence
2. **Content Hashing** - SHA256 hash computed from `"{timestamp}:{content}"`
3. **Optional Signature** - Hash is signed with investigator's GPG private key
**Mathematical Representation:**
```
hash = SHA256(timestamp + ":" + content)
timestamp = Unix epoch time as float (e.g., 1702345678.123456)
hash_input = "{timestamp}:{content}"
hash = SHA256(hash_input)
signature = GPG_Sign(hash, private_key)
```
**Example:**
```
Content: "Suspicious process detected"
Timestamp: 1702345678.123456
Hash input: "1702345678.123456:Suspicious process detected"
Hash: SHA256 of above = a3f5b2c8d9e1f4a7b6c3d8e2f5a9b4c7d1e6f3a8b5c2d9e4f7a1b8c6d3e0f5a2
```
**Security Properties:**
- **Temporal Integrity**: Timestamp is cryptographically bound to content (cannot backdate notes)
- **Tamper Detection**: Any modification to content or timestamp invalidates the hash
- **Non-Repudiation**: GPG signature proves who created the note (if signing enabled)
- **Hash Reproducibility**: Exported markdown includes Unix timestamp for independent verification
- **Efficient Storage**: Signing only the hash (64 hex chars) instead of full content
### Layer 2: Export-Level Integrity (On Demand)
@@ -328,6 +341,26 @@ Individual note signatures are embedded in the markdown export. To verify a spec
- The GPG signature proves who created that hash
- Together: Proves this specific content was created by this investigator at this time
**Hash Verification (Manual):**
To independently verify a note's hash from the markdown export:
1. Locate the note in the export file and extract:
- Unix Timestamp (e.g., `1702345678.123456`)
- Content (e.g., `"Suspicious process detected"`)
- Claimed Hash (e.g., `a3f5b2c8...`)
2. Recompute the hash:
```bash
# Using Python
python3 -c "import hashlib; print(hashlib.sha256(b'1702345678.123456:Suspicious process detected').hexdigest())"
# Using command-line tools
echo -n "1702345678.123456:Suspicious process detected" | sha256sum
```
3. Compare the computed hash with the claimed hash - they must match exactly
### Cryptographic Trust Model
```

View File

@@ -364,9 +364,14 @@ def export_markdown(output_file: str = "export.md"):
sys.exit(1)
def format_note_for_export(note: Note) -> str:
"""Format a single note for export (returns string instead of writing to file)"""
"""Format a single note for export (returns string instead of writing to file)
Includes Unix timestamp for hash reproducibility - anyone can recompute the hash
using the formula: SHA256("{unix_timestamp}:{content}")
"""
lines = []
lines.append(f"- **{time.ctime(note.timestamp)}**\n")
lines.append(f" - Unix Timestamp: `{note.timestamp}` (for hash verification)\n")
lines.append(f" - Content:\n")
# Properly indent multi-line content
for line in note.content.splitlines():

View File

@@ -43,12 +43,25 @@ class Crypto:
return False, "Not a GPG signed message"
try:
# Force English output for consistent parsing across locales
# Linux/macOS: LC_ALL/LANG variables control GPG's output language
# Windows: GPG may ignore these, but encoding='utf-8' + errors='replace' provides robustness
import os
env = os.environ.copy()
# Use C.UTF-8 for English messages with UTF-8 encoding support
# Falls back gracefully via errors='replace' if locale not available
env['LC_ALL'] = 'C.UTF-8'
env['LANG'] = 'C.UTF-8'
proc = subprocess.Popen(
['gpg', '--verify'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
text=True,
encoding='utf-8',
errors='replace', # Handle encoding issues on any platform
env=env
)
stdout, stderr = proc.communicate(input=signed_content, timeout=10)
@@ -171,5 +184,25 @@ class Crypto:
@staticmethod
def hash_content(content: str, timestamp: float) -> str:
"""Calculate SHA256 hash of timestamp:content.
Hash input format: "{timestamp}:{content}"
- timestamp: Unix epoch timestamp as float (seconds since 1970-01-01 00:00:00 UTC)
Example: 1702345678.123456
- The float is converted to string using Python's default str() conversion
- Colon (':') separator between timestamp and content
- Ensures integrity of both WHAT was said and WHEN it was said
Args:
content: The note content to hash
timestamp: Unix epoch timestamp as float
Returns:
SHA256 hash as hexadecimal string (64 characters)
Example:
>>> hash_content("Suspicious process detected", 1702345678.123456)
Computes SHA256 of: "1702345678.123456:Suspicious process detected"
"""
data = f"{timestamp}:{content}".encode('utf-8')
return hashlib.sha256(data).hexdigest()

View File

@@ -12,6 +12,9 @@ from .extractors import TagExtractor, IOCExtractor
@dataclass
class Note:
content: str
# Unix timestamp: seconds since 1970-01-01 00:00:00 UTC as float
# Example: 1702345678.123456
# This exact float value (with full precision) is used in hash calculation
timestamp: float = field(default_factory=time.time)
note_id: str = field(default_factory=lambda: str(uuid.uuid4()))
content_hash: str = ""
@@ -28,7 +31,16 @@ class Note:
self.iocs = IOCExtractor.extract_iocs(self.content)
def calculate_hash(self):
# We hash the content + timestamp to ensure integrity of 'when' it was said
"""Calculate SHA256 hash of timestamp:content.
Hash input format: "{timestamp}:{content}"
- timestamp: Unix epoch timestamp as float (e.g., "1702345678.123456")
- The float is converted to string using Python's default str() conversion
- Colon separator between timestamp and content
- Ensures integrity of both WHAT was said and WHEN it was said
Example hash input: "1702345678.123456:Suspicious process detected"
"""
data = f"{self.timestamp}:{self.content}".encode('utf-8')
self.content_hash = hashlib.sha256(data).hexdigest()

View File

@@ -222,15 +222,23 @@ class ExportHandler:
@staticmethod
def _write_note_markdown(f, note: Note):
"""Helper to write a note in markdown format"""
"""Helper to write a note in markdown format
Includes Unix timestamp for hash reproducibility - anyone can recompute the hash
using the formula: SHA256("{unix_timestamp}:{content}")
"""
f.write(f"- **{time.ctime(note.timestamp)}**\n")
f.write(f" - Content: {note.content}\n")
f.write(f" - Unix Timestamp: `{note.timestamp}` (for hash verification)\n")
f.write(f" - Content:\n")
# Properly indent multi-line content
for line in note.content.splitlines():
f.write(f" {line}\n")
if note.tags:
tags_str = " ".join([f"#{tag}" for tag in note.tags])
f.write(f" - Tags: {tags_str}\n")
f.write(f" - Hash: `{note.content_hash}`\n")
f.write(f" - SHA256 Hash (timestamp:content): `{note.content_hash}`\n")
if note.signature:
f.write(" - **Signature Verified:**\n")
f.write(" - **GPG Signature of Hash:**\n")
f.write(" ```\n")
for line in note.signature.splitlines():
f.write(f" {line}\n")

View File

@@ -39,5 +39,5 @@ def init_colors():
curses.init_pair(ColorPairs.TAG, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
# IOCs on selected background (red on cyan)
curses.init_pair(ColorPairs.IOC_SELECTED, curses.COLOR_RED, curses.COLOR_CYAN)
# Tags on selected background (yellow on cyan)
curses.init_pair(ColorPairs.TAG_SELECTED, curses.COLOR_YELLOW, curses.COLOR_CYAN)
# Tags on selected background (magenta on cyan)
curses.init_pair(ColorPairs.TAG_SELECTED, curses.COLOR_MAGENTA, curses.COLOR_CYAN)

View File

@@ -113,15 +113,15 @@ class TextRenderer:
screen.addstr(y, x_pos, text)
screen.attroff(curses.color_pair(ColorPairs.ERROR) | curses.A_BOLD)
else: # tag
# Tag highlighting: yellow on cyan if selected, yellow on black otherwise
# Tag highlighting: magenta on cyan if selected, magenta on black otherwise
if is_selected:
screen.attron(curses.color_pair(ColorPairs.TAG_SELECTED))
screen.addstr(y, x_pos, text)
screen.attroff(curses.color_pair(ColorPairs.TAG_SELECTED))
else:
screen.attron(curses.color_pair(ColorPairs.WARNING))
screen.attron(curses.color_pair(ColorPairs.TAG))
screen.addstr(y, x_pos, text)
screen.attroff(curses.color_pair(ColorPairs.WARNING))
screen.attroff(curses.color_pair(ColorPairs.TAG))
x_pos += len(text)
last_pos = end

View File

@@ -0,0 +1,88 @@
"""Visual constants for consistent TUI layout and styling"""
class Layout:
"""Screen layout constants"""
HEADER_Y = 0
HEADER_X = 2
CONTENT_START_Y = 2
CONTENT_INDENT = 4
FOOTER_OFFSET_FROM_BOTTOM = 3
BORDER_OFFSET_FROM_BOTTOM = 2
STATUS_LINE_OFFSET_FROM_BOTTOM = 1 # height - 1 for status bar
NOTE_DETAIL_BOTTOM_RESERVE = 6 # height - 6 for note detail view
class Spacing:
"""Spacing and padding constants"""
SECTION_VERTICAL_GAP = 2
ITEM_VERTICAL_GAP = 1
DIALOG_MARGIN = 4
HORIZONTAL_PADDING = 6 # width - 6 for truncation
HASH_DISPLAY_PADDING = 20 # width - 20
HASH_SHORT_PADDING = 12 # width - 12 for shorter hash displays
EMPTY_STATE_PADDING = 8 # width - 8 for empty state boxes
STATUS_BAR_PADDING = 2 # width - 2 for status bar
class ColumnWidths:
"""Column widths for list displays - can be percentage-based"""
TAG_COLUMN_MIN = 30
IOC_COLUMN_MIN = 50
CONTENT_PREVIEW_MIN = 50
NOTE_PREVIEW_MIN = 60
@staticmethod
def get_tag_width(terminal_width):
"""Get responsive tag column width (40% of terminal or min 30)"""
return max(ColumnWidths.TAG_COLUMN_MIN, int(terminal_width * 0.4))
@staticmethod
def get_ioc_width(terminal_width):
"""Get responsive IOC column width (50% of terminal or min 50)"""
return max(ColumnWidths.IOC_COLUMN_MIN, int(terminal_width * 0.5))
@staticmethod
def get_content_preview_width(terminal_width):
"""Get responsive content preview width (50% of terminal or min 50)"""
return max(ColumnWidths.CONTENT_PREVIEW_MIN, int(terminal_width * 0.5))
class DialogSize:
"""Standard dialog dimensions (width, height)"""
SMALL = (40, 8) # Confirm dialogs
MEDIUM = (60, 15) # Settings, single input
LARGE = (70, 20) # Multiline, help
class Icons:
"""Unicode symbols used throughout UI"""
ACTIVE = ""
INACTIVE = ""
DIAMOND = ""
SQUARE = ""
SMALL_SQUARE = ""
ARROW_RIGHT = ""
WARNING = ""
HASH = ""
FILTER = ""
VERIFIED = ""
FAILED = ""
UNSIGNED = "?"
SEPARATOR_H = ""
SEPARATOR_V = ""
SEPARATOR_GROUP = "" # For grouping footer commands
BOX_TL = ""
BOX_BL = ""
# Box drawing for improved empty states
BOX_DOUBLE_TL = ""
BOX_DOUBLE_TR = ""
BOX_DOUBLE_BL = ""
BOX_DOUBLE_BR = ""
BOX_DOUBLE_H = ""
BOX_DOUBLE_V = ""
class Timing:
"""Timing constants"""
FLASH_MESSAGE_DURATION = 3 # seconds

File diff suppressed because it is too large Load Diff