Skip to content

Commit 429b381

Browse files
committed
update adding claude.md
1 parent fd49d89 commit 429b381

12 files changed

Lines changed: 1339 additions & 47 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/sh
2+
if grep -q "caliber" .git/hooks/pre-commit 2>/dev/null; then
3+
exit 0
4+
fi
5+
FLAG="/tmp/caliber-nudge-$(echo "$PWD" | shasum | cut -c1-8)"
6+
find /tmp -maxdepth 1 -name "caliber-nudge-*" -mmin +120 -delete 2>/dev/null
7+
if [ -f "$FLAG" ]; then
8+
exit 0
9+
fi
10+
touch "$FLAG"
11+
printf '{"decision":"block","reason":"Caliber agent config sync is not set up on this machine. Tell the user: This project uses Caliber to keep agent configs in sync — it is not set up yet. Want me to run /setup-caliber? (~30 seconds)"}'

.claude/settings.json

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{
2+
"hooks": {
3+
"Stop": [
4+
{
5+
"matcher": "",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": ".claude/hooks/caliber-check-sync.sh",
10+
"description": "Caliber: offer setup if not configured"
11+
}
12+
]
13+
}
14+
],
15+
"PostToolUse": [
16+
{
17+
"matcher": "",
18+
"hooks": [
19+
{
20+
"type": "command",
21+
"command": "caliber learn observe",
22+
"description": "Caliber: recording tool usage for session learning"
23+
}
24+
]
25+
}
26+
],
27+
"PostToolUseFailure": [
28+
{
29+
"matcher": "",
30+
"hooks": [
31+
{
32+
"type": "command",
33+
"command": "caliber learn observe --failure",
34+
"description": "Caliber: recording tool failure for session learning"
35+
}
36+
]
37+
}
38+
],
39+
"UserPromptSubmit": [
40+
{
41+
"matcher": "",
42+
"hooks": [
43+
{
44+
"type": "command",
45+
"command": "caliber learn observe --prompt",
46+
"description": "Caliber: recording user prompt for correction detection"
47+
}
48+
]
49+
}
50+
],
51+
"SessionEnd": [
52+
{
53+
"matcher": "",
54+
"hooks": [
55+
{
56+
"type": "command",
57+
"command": "caliber learn finalize --auto",
58+
"description": "Caliber: finalizing session learnings"
59+
}
60+
]
61+
},
62+
{
63+
"matcher": "",
64+
"hooks": [
65+
{
66+
"type": "command",
67+
"command": "caliber refresh --quiet",
68+
"description": "Caliber: auto-refreshing docs based on code changes"
69+
}
70+
]
71+
}
72+
]
73+
},
74+
"permissions": {
75+
"allow": [
76+
"Bash(git *)"
77+
]
78+
}
79+
}
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
---
2+
name: admin-page-function
3+
description: Creates a new admin page function in `src/` following `reusable_fantastico.php` / `fantastico_licenses_list.php` patterns: PHP file with named function, `page_title()`, admin check via `$GLOBALS['tf']->ima == 'admin'`, `TFTable` for tabular output, `add_output()` for rendering, and `render_form()` for form-based pages. Registers the page in `Plugin::getRequirements()` via `$loader->add_page_requirement()` and optionally adds a menu link in `Plugin::getMenu()`. Use when user says 'add admin page', 'new page', 'list view', or adds files to `src/`. Do NOT use for non-admin pages, API endpoints, or hook handlers.
4+
---
5+
# Admin Page Function
6+
7+
## Critical
8+
9+
- **Always** gate the entire page body with `if ($GLOBALS['tf']->ima == 'admin')` — never render output for non-admins.
10+
- **Never** echo directly — always use `add_output()` to buffer content.
11+
- **Never** build INSERT strings manually — use `make_insert_query($table, $assoc)` for all inserts.
12+
- **Always** escape user input with `$db->real_escape()` before using it in a query.
13+
- The function name **must exactly match** the filename (e.g. `src/reusable_fantastico.php``function reusable_fantastico()`).
14+
- `$loader->add_page_requirement()` path uses the `src/` directory prefix — **not** an absolute path.
15+
16+
## Instructions
17+
18+
### Step 1 — Create the page file in `src/`
19+
20+
Create the page file in `src/` with this boilerplate:
21+
22+
```php
23+
<?php
24+
/**
25+
* Fantastico Related Functionality
26+
* @author Joe Huss <detain@interserver.net>
27+
* @copyright 2025
28+
* @package MyAdmin
29+
* @category Licenses
30+
*/
31+
32+
use Detain\Fantastico\Fantastico;
33+
34+
function page_function_name()
35+
{
36+
page_title('<Human Readable Title>');
37+
if ($GLOBALS['tf']->ima == 'admin') {
38+
$module = 'licenses';
39+
$db = get_module_db($module);
40+
$settings = \get_module_settings($module); // provides PREFIX, TABLE, TBLNAME
41+
42+
// ... page logic ...
43+
44+
add_output($table->get_table());
45+
}
46+
}
47+
```
48+
49+
Verify: function name matches filename exactly before proceeding.
50+
51+
### Step 2 — Choose the rendering pattern
52+
53+
**Pattern A — TFTable (data list, like `src/fantastico_licenses_list.php`):**
54+
55+
```php
56+
$table = new \TFTable();
57+
$table->set_title('My Table Title');
58+
$header = false;
59+
foreach ($rows as $data) {
60+
if (!$header) {
61+
foreach (array_keys($data) as $field) {
62+
$table->add_field(ucwords(str_replace('_', ' ', $field)));
63+
}
64+
$table->add_row();
65+
$header = true;
66+
}
67+
foreach (array_values($data) as $field) {
68+
$table->add_field($field);
69+
}
70+
$table->add_row();
71+
}
72+
add_output($table->get_table());
73+
```
74+
75+
**Pattern B — TFTable with form input (like `src/reusable_fantastico.php`):**
76+
77+
```php
78+
add_output('<h3>Section Heading</h3>');
79+
$table = new \TFTable();
80+
$table->add_hidden('add', 1);
81+
$table->set_title('Form Title');
82+
$table->add_field('Label');
83+
$table->add_field($table->make_input('field_name', '', 20));
84+
$table->add_field($table->make_submit('Submit'));
85+
$table->add_row();
86+
add_output($table->get_table());
87+
add_output(render_form('page_function_name'));
88+
```
89+
90+
**Pattern C — Pure Smarty form (like `src/fantastico_list.php`):**
91+
92+
```php
93+
add_output(render_form('page_function_name'));
94+
```
95+
96+
Verify: you know whether the page needs a data table, input form, or Smarty template before choosing.
97+
98+
### Step 3 — Handle form submission (Pattern B only)
99+
100+
Add this block **before** the output section, inside the `if ($GLOBALS['tf']->ima == 'admin')` block:
101+
102+
```php
103+
if (isset($GLOBALS['tf']->variables->request['add']) && $GLOBALS['tf']->variables->request['add'] == 1) {
104+
$field = $db->real_escape($GLOBALS['tf']->variables->request['field_name']);
105+
// validate, then insert:
106+
$db->query(make_insert_query($settings['TABLE'], [
107+
$settings['PREFIX'].'_id' => null,
108+
$settings['PREFIX'].'_custid' => 8,
109+
$settings['PREFIX'].'_order_date' => mysql_now(),
110+
$settings['PREFIX'].'_status' => 'active',
111+
// ... other columns ...
112+
]), __LINE__, __FILE__);
113+
// on validation error:
114+
// dialog('Error', 'Descriptive message here');
115+
}
116+
```
117+
118+
Verify: every `$_REQUEST` value is passed through `$db->real_escape()` before use.
119+
120+
### Step 4 — Register the page in `Plugin::getRequirements()`
121+
122+
Open `src/Plugin.php` and add inside `getRequirements()`, alongside the existing `add_page_requirement` calls:
123+
124+
```php
125+
$loader->add_page_requirement('page_function_name', 'src/page_function_name.php');
126+
```
127+
128+
Verify: the path starts with `src/` and the filename matches the function name exactly.
129+
130+
### Step 5 — Add a menu link in `Plugin::getMenu()` (optional but typical)
131+
132+
Inside `getMenu()`, within the `if ($GLOBALS['tf']->ima == 'admin')` block:
133+
134+
```php
135+
$menu->add_link(self::$module, 'choice=none.page_function_name', '/images/myadmin/list.png', _('Menu Label'));
136+
```
137+
138+
Verify: `choice=none.page_function_name` uses the exact function/file name registered in Step 4.
139+
140+
## Examples
141+
142+
**User says:** "Add an admin page that lists all canceled Fantastico licenses."
143+
144+
**Actions taken:**
145+
146+
1. Create `src/fantastico_canceled_list.php`:
147+
```php
148+
<?php
149+
/**
150+
* Fantastico Related Functionality
151+
* @author Joe Huss <detain@interserver.net>
152+
* @copyright 2025
153+
* @package MyAdmin
154+
* @category Licenses
155+
*/
156+
157+
use Detain\Fantastico\Fantastico;
158+
159+
function fantastico_canceled_list()
160+
{
161+
page_title('Canceled Fantastico Licenses');
162+
if ($GLOBALS['tf']->ima == 'admin') {
163+
$module = 'licenses';
164+
$db = get_module_db($module);
165+
$settings = \get_module_settings($module);
166+
$db->query("SELECT * FROM {$settings['TABLE']} WHERE {$settings['PREFIX']}_status = 'canceled'", __LINE__, __FILE__);
167+
$table = new \TFTable();
168+
$table->set_title('Canceled Fantastico Licenses');
169+
$header = false;
170+
while ($db->next_record(MYSQL_ASSOC)) {
171+
$data = $db->Record;
172+
if (!$header) {
173+
foreach (array_keys($data) as $field) {
174+
$table->add_field(ucwords(str_replace('_', ' ', $field)));
175+
}
176+
$table->add_row();
177+
$header = true;
178+
}
179+
foreach (array_values($data) as $field) {
180+
$table->add_field($field);
181+
}
182+
$table->add_row();
183+
}
184+
add_output($table->get_table());
185+
}
186+
}
187+
```
188+
189+
2. In `src/Plugin.php``getRequirements()`:
190+
```php
191+
$loader->add_page_requirement('fantastico_canceled_list', 'src/fantastico_canceled_list.php');
192+
```
193+
194+
3. In `src/Plugin.php``getMenu()`:
195+
```php
196+
$menu->add_link(self::$module, 'choice=none.fantastico_canceled_list', '/images/myadmin/list.png', _('Canceled Fantastico Licenses'));
197+
```
198+
199+
**Result:** Page is accessible at `?choice=none.fantastico_canceled_list`, visible only to admins, listed in the licenses module menu.
200+
201+
## Common Issues
202+
203+
**Page renders blank / no output for admin users:**
204+
- The function name does not match the filename. For example, `src/reusable_fantastico.php` must contain `function reusable_fantastico()` — check both.
205+
- `add_page_requirement` key does not match the function name — `choice=none.X` dispatches to function `X()`.
206+
207+
**"Undefined function" error on page load:**
208+
- The `add_page_requirement()` path is wrong. Verify it starts with `src/` and the filename matches exactly (case-sensitive on Linux).
209+
210+
**Form submission silently does nothing:**
211+
- Check `isset($GLOBALS['tf']->variables->request['add'])` — POST/GET values come from `$GLOBALS['tf']->variables->request`, not `$_POST` or `$_GET`.
212+
213+
**SQL error on insert:**
214+
- Never interpolate raw request values. Always call `$db->real_escape()` first, or use `make_insert_query()` which handles escaping.
215+
- `make_insert_query()` requires the full column name including prefix (e.g. `license_ip`, not `ip`).
216+
217+
**Menu link not appearing:**
218+
- The `getMenu()` handler checks `$GLOBALS['tf']->ima == 'admin'` — confirm you are logged in as admin.
219+
- `$menu->add_link()` first argument must match `self::$module` (`'licenses'`), not a custom string.
220+
221+
**`page_title()` not called for form-only pages (Pattern C):**
222+
- `src/fantastico_list.php` omits `page_title()` — this is valid for pages that delegate entirely to `render_form()`. Only call `page_title()` when the page has its own heading.

0 commit comments

Comments
 (0)