From db78ee3f8cc06fc473a9b40759d340a679c552ac Mon Sep 17 00:00:00 2001
From: Vuong <3168632+vuon9@users.noreply.github.com>
Date: Sun, 1 Feb 2026 13:11:37 +0700
Subject: [PATCH 1/2] feat: Add layout toggle for all other pages
---
AGENTS.md | 58 +++++++++++++++++----
README.md | 9 ++--
TOOL_STATUS.md | 53 --------------------
src/pages/BarcodeGenerator.jsx | 20 +++++++-
src/pages/CodeFormatter/index.jsx | 20 +++++++-
src/pages/CsvJsonConverter.jsx | 83 -------------------------------
src/pages/DataGenerator/index.jsx | 19 ++++++-
src/pages/YamlToJson.jsx | 71 --------------------------
8 files changed, 105 insertions(+), 228 deletions(-)
delete mode 100644 src/pages/CsvJsonConverter.jsx
delete mode 100644 src/pages/YamlToJson.jsx
diff --git a/AGENTS.md b/AGENTS.md
index 57e286c..ff5bc58 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -32,6 +32,41 @@ Every tool must follow this hierarchy:
2. **Controls**: A distinct row/area for options (selects, inputs) and primary actions (buttons).
3. **Workspace**: The main area, usually split 50/50 between Input and Output panes.
+### Layout Toggle (Horizontal/Vertical)
+All tools with split-pane layouts **MUST** include a layout toggle button to allow users to switch between horizontal and vertical arrangements.
+
+**Implementation Pattern:**
+```jsx
+import useLayoutToggle from '../hooks/useLayoutToggle';
+import { ToolLayoutToggle } from '../components/ToolUI';
+
+// In component:
+const layout = useLayoutToggle({
+ toolKey: 'unique-tool-key-layout',
+ defaultDirection: 'horizontal',
+ showToggle: true,
+ persist: true
+});
+
+// In ToolControls:
+
+
+
+
+// On ToolSplitPane:
+
+```
+
+**Requirements:**
+- Place the toggle button at the end of the `ToolControls` area using `marginLeft: 'auto'`
+- Use a unique `toolKey` for each tool to ensure independent persistence
+- Always set `persist: true` to remember user's preference
+- Update `ToolSplitPane` to dynamically adjust `columnCount` based on layout direction
+
### Component Specifics
#### Buttons
@@ -239,7 +274,7 @@ func TestDecode(t *testing.T) {
## 7. Development Setup
### Prerequisites
-- **Node.js** (>= 18)
+- **Bun** (>= 1.0) - Required for frontend dependencies
- **Go** (>= 1.22)
- **Wails CLI** (`go install github.com/wailsapp/wails/v2/cmd/wails@latest`)
@@ -249,7 +284,7 @@ func TestDecode(t *testing.T) {
git clone https://github.com/vuon9/dev-toolbox.git
cd dev-toolbox
-# Install frontend dependencies (using Bun)
+# Install dependencies (using Bun)
bun install
```
@@ -304,7 +339,7 @@ bun run format
Follow this step‑by‑step guide to add a new tool component:
1. **Create Component File**
- - Create a new `.jsx` file in `frontend/src/tools/` (e.g., `MyNewTool.jsx`).
+ - Create a new `.jsx` file in `src/pages/` (e.g., `MyNewTool.jsx`).
- Use the existing tool components as reference (e.g., `JwtDebugger.jsx`).
2. **Implement the Tool**
@@ -316,15 +351,19 @@ Follow this step‑by‑step guide to add a new tool component:
- Ensure input/output panes are symmetrical, have monospace fonts, and include copy buttons.
3. **Add Route**
- - Open `frontend/src/App.jsx`.
+ - Open `src/App.jsx`.
- Import your new component.
- - Add a route entry in the `routes` array:
+ - Add the component to the `renderTool()` switch statement:
```jsx
- { path: '/my-new-tool', element: }
+ case 'my-new-tool': return ;
```
-4. **Update Sidebar** (Optional)
- - If the tool should appear in the sidebar, add an entry in `frontend/src/App.jsx` within the `sidebarItems` array.
+4. **Update Sidebar**
+ - Open `src/components/Sidebar.jsx`.
+ - Add an entry to the `tools` array:
+ ```jsx
+ { id: 'my-new-tool', name: 'My New Tool', icon: '🛠️' }
+ ```
5. **Test the Tool**
- Run `wails dev` to verify the tool works correctly.
@@ -443,11 +482,12 @@ These guidelines are intended for AI assistants (like opencode) working on this
1. **Run linting & formatting** – Execute any available lint/format commands (see section 8).
2. **Test the tool** – Verify functionality with `wails dev`.
3. **Update `TOOL_STATUS.md`** – Update the tool's status and add completion notes (mark as 🟢 Done when complete).
-4. **Update `README.md`** – **Whenever TOOL_STATUS.md is updated, review README.md to ensure consistency**:
+4. **Update `README.md`** – **REQUIRED: Whenever TOOL_STATUS.md is updated, README.md MUST also be updated to ensure consistency**:
- Add new tools to the feature table
- Remove deprecated tools
- Update descriptions if features changed
- Keep the tool count/feature list in sync
+ - **This is mandatory** - never update TOOL_STATUS.md without checking/updating README.md
5. **Commit changes** – Use descriptive commit messages that reference the tool name and changes made.
### Important Notes
diff --git a/README.md b/README.md
index adce7ec..5c372df 100644
--- a/README.md
+++ b/README.md
@@ -41,13 +41,10 @@ The central hub with 45+ algorithms across 5 categories:
| **Code Formatter** | Format and minify JSON, XML, HTML, SQL, CSS, and JavaScript with advanced filtering support (jq for JSON, XPath for XML, CSS selectors for HTML) |
| **RegExp Tester** | Test regular expressions with real-time matching |
| **Unix Time Converter** | Convert between Unix timestamps and human-readable dates |
-| **String Case Converter** | Convert between camelCase, snake_case, PascalCase, etc. |
+| **String Utilities** | Sort/Dedupe lines, Case conversion (camelCase, snake_case, etc.), String Inspector |
| **Cron Job Parser** | Parse and explain cron expressions |
| **Text Diff Checker** | Compare two text blocks and highlight differences |
-| **Line Sort / Dedupe** | Sort lines, remove duplicates, trim whitespace |
-| **String Inspector** | Count characters, words, lines, bytes, and sentences |
-| **PHP Serializer** | PHP serialization/unserialization |
-| **URL Tools** | Parse URLs, extract components |
+| **Number Converter** | Convert between Decimal, Hex, Octal, and Binary |
## Installation
@@ -62,7 +59,7 @@ Download the latest release for your platform from the [Releases](https://github
### Build from Source
**Prerequisites:**
-- Node.js (>= 18)
+- Bun (>= 1.0) - Required for frontend dependencies
- Go (>= 1.22)
- Wails CLI: `go install github.com/wailsapp/wails/v2/cmd/wails@latest`
diff --git a/TOOL_STATUS.md b/TOOL_STATUS.md
index a3ae59e..32df695 100644
--- a/TOOL_STATUS.md
+++ b/TOOL_STATUS.md
@@ -26,59 +26,6 @@ This document tracks the refactoring and development status of each tool compone
| **TextDiffChecker** | 🟡 In Progress | Refactored with enhanced features. Features: Diff mode switcher (Lines/Words/Chars), auto-compare on input change, Clear button, improved diff view with color coding, layout toggle. | Updated 2026-01-31 |
| **UnixTimeConverter** | 🟡 In Progress | Refactored with new features. Features: Relative time display (e.g., "2 hours ago"), split-pane layout (ISO 8601 / Local), "Now" button with icon, layout toggle, auto-initialization with current time. | Updated 2026-01-31 |
-### Removed Tools (Consolidated)
-
-| Tool | Replacement | Reason |
-|------|-------------|--------|
-| JsonFormatter | CodeFormatter | Unified formatting tool |
-| SqlFormatter | CodeFormatter | Unified formatting tool |
-| UrlTools | TextConverter | URL encode/decode functionality |
-| UrlParser | TextConverter | URL parsing functionality |
-| UrlEncoder | TextConverter | URL encoding functionality |
-| PhpSerializer | - | Removed - low usage |
-| PhpJsonConverter | - | Removed - low usage |
-| LineSortDedupe | StringUtilities | Consolidated into StringUtilities |
-| StringCaseConverter | StringUtilities | Consolidated into StringUtilities |
-| StringInspector | StringUtilities | Consolidated into StringUtilities |
-| NumberBaseConverter | NumberConverter | Renamed and refactored |
-| TextBasedConverter | TextConverter | Renamed for clarity |
-
----
-
-## Final Tool Count: 11 Tools
-
-1. **Text Converter** - Encoding, encryption, hashing, escaping
-2. **String Utilities** - Sort/Dedupe, Case conversion, Inspector
-3. **Number Converter** - Decimal, Hex, Octal, Binary conversions
-4. **Unix Time Converter** - Timestamp conversions with relative time
-5. **JWT Debugger** - JWT encode/decode/verify
-6. **RegExp Tester** - Regular expression testing
-7. **Cron Job Parser** - Cron expression parsing
-8. **Text Diff Checker** - Text comparison
-9. **Code Formatter** - JSON, XML, HTML, SQL, CSS, JS formatting
-10. **Barcode Generator** - QR codes and barcodes
-11. **Data Generator** - Mock data generation
-
----
-
-## Refactoring Checklist
-
-When refactoring a tool, ensure:
-
-- [x] Uses **Carbon Design System** components (`@carbon/react`)
-- [x] All colors use `var(--cds-*)` tokens, no hardcoded hex values
-- [x] Implements **useReducer** for state management (not multiple useState hooks)
-- [x] Uses **useCallback** for memoized functions
-- [x] Follows **DRY principle** - no duplicated components/logic
-- [x] Has proper **ToolHeader** with title and description
-- [x] Input/Output panes are symmetrical and use **Carbon TextArea**
-- [x] All buttons properly spaced (gap: 1rem)
-- [x] Copy buttons present on all output/data panes
-- [x] Monospace font for data (`'IBM Plex Mono', monospace`)
-- [x] Proper flex layout for responsive sizing
-- [x] No unused imports or variables
-- [x] Code compiles without errors or warnings
-
---
## How to Update This File
diff --git a/src/pages/BarcodeGenerator.jsx b/src/pages/BarcodeGenerator.jsx
index 583f1ae..e929c86 100644
--- a/src/pages/BarcodeGenerator.jsx
+++ b/src/pages/BarcodeGenerator.jsx
@@ -1,7 +1,8 @@
import React, { useState, useCallback, useRef } from 'react';
import { Button, Dropdown, InlineLoading } from '@carbon/react';
import { Renew, Download } from '@carbon/icons-react';
-import { ToolHeader, ToolPane, ToolSplitPane } from '../components/ToolUI';
+import { ToolHeader, ToolPane, ToolSplitPane, ToolLayoutToggle } from '../components/ToolUI';
+import useLayoutToggle from '../hooks/useLayoutToggle';
import { Backend } from '../utils/backendBridge';
const BARCODE_STANDARDS = [
@@ -119,6 +120,13 @@ export default function BarcodeGenerator() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
+ const layout = useLayoutToggle({
+ toolKey: 'barcode-generator-layout',
+ defaultDirection: 'horizontal',
+ showToggle: true,
+ persist: true
+ });
+
// Track what was last generated to prevent duplicate generation
const lastGeneratedParams = useRef({ content: '', standard: 'QR', size: 256, level: 'M' });
@@ -288,9 +296,17 @@ export default function BarcodeGenerator() {
>
Generate
+
+
+
+
-
+
{/* Input Pane */}
{
localStorage.setItem(STORAGE_KEY, JSON.stringify({
@@ -206,6 +214,14 @@ export default function CodeFormatter() {
+
+
+
+
@@ -221,7 +237,7 @@ export default function CodeFormatter() {
)}
-
+
{
- if (!input.trim()) {
- setOutput('');
- setError('');
- return;
- }
- setError('');
-
- if (mode === 'csv2json') {
- Papa.parse(input, {
- header: true,
- skipEmptyLines: true,
- complete: (results) => {
- if (results.errors.length > 0) {
- setError('CSV Parse Error: ' + results.errors[0].message);
- setOutput('');
- } else {
- setOutput(JSON.stringify(results.data, null, 2));
- }
- },
- error: (err) => {
- setError('CSV Parse Error: ' + err.message);
- setOutput('');
- }
- });
- } else {
- try {
- const json = JSON.parse(input);
- const csv = Papa.unparse(json);
- setOutput(csv);
- } catch (e) {
- setError('Invalid JSON: ' + e.message);
- setOutput('');
- }
- }
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- {error &&
{error}
}
-
-
- setInput(e.target.value)}
- />
-
-
-
- );
-}
diff --git a/src/pages/DataGenerator/index.jsx b/src/pages/DataGenerator/index.jsx
index 17ac4f5..8c4c82f 100644
--- a/src/pages/DataGenerator/index.jsx
+++ b/src/pages/DataGenerator/index.jsx
@@ -1,6 +1,7 @@
import React, { useEffect, useCallback } from 'react';
import { InlineNotification } from '@carbon/react';
-import { ToolHeader, ToolControls, ToolPane, ToolSplitPane } from '../../components/ToolUI';
+import { ToolHeader, ToolControls, ToolPane, ToolSplitPane, ToolLayoutToggle } from '../../components/ToolUI';
+import useLayoutToggle from '../../hooks/useLayoutToggle';
import { Backend } from '../../utils/backendBridge';
import { initialState, reducer } from './constants';
import GeneratorControls from './components/GeneratorControls';
@@ -10,6 +11,13 @@ import HelpModal from './components/HelpModal';
export default function DataGenerator() {
const [state, dispatch] = React.useReducer(reducer, initialState);
+ const layout = useLayoutToggle({
+ toolKey: 'data-generator-layout',
+ defaultDirection: 'horizontal',
+ showToggle: true,
+ persist: true
+ });
+
// Load presets on mount
useEffect(() => {
const loadPresets = async () => {
@@ -131,6 +139,13 @@ export default function DataGenerator() {
onPresetChange={handlePresetChange}
onGenerate={handleGenerate}
/>
+
+
+
-
+
{
- const convert = () => {
- if (!input.trim()) {
- setOutput('');
- setError('');
- return;
- }
-
- try {
- if (mode === 'yaml2json') {
- const obj = yaml.load(input);
- setOutput(JSON.stringify(obj, null, 2));
- } else {
- const obj = JSON.parse(input);
- setOutput(yaml.dump(obj));
- }
- setError('');
- } catch (e) {
- setError(e.message);
- setOutput('');
- }
- };
- convert();
- }, [input, mode]);
-
- return (
-
-
-
-
-
-
-
-
-
-
- {error &&
{error}
}
-
-
- setInput(e.target.value)}
- placeholder={mode === 'yaml2json' ? 'Enter YAML...' : 'Enter JSON...'}
- />
-
-
-
- );
-}
From cf86664fcd2228978c6cb832d7e84699159a8af5 Mon Sep 17 00:00:00 2001
From: Vuong <3168632+vuon9@users.noreply.github.com>
Date: Sun, 1 Feb 2026 13:27:58 +0700
Subject: [PATCH 2/2] try: gh opencode
---
.github/workflows/opencode.yml | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml
index a82b792..50c5e60 100644
--- a/.github/workflows/opencode.yml
+++ b/.github/workflows/opencode.yml
@@ -21,13 +21,14 @@ jobs:
issues: read
steps:
- name: Checkout repository
- uses: actions/checkout@v6
- with:
- persist-credentials: false
+ uses: actions/checkout@v4
+
+ - uses: ./.github/actions/setup-bun
- name: Run opencode
uses: anomalyco/opencode/github@latest
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+ OPENCODE_PERMISSION: '{"bash": "deny"}'
with:
model: opencode/kimi-k2.5-free