Jump to content

psy

Members
  • Posts

    733
  • Joined

  • Last visited

  • Days Won

    11

Posts posted by psy

  1. ProcessPromptManager is a ProcessWire admin module for generating site-aware prompt exports for external AI agents.

    It allows an administrator to select a template, define which fields an agent should populate, add instructions, and export a zip containing a markdown prompt and JSON field definitions.

    The module does not handle authentication or integration. It provides a controlled starting point for accepting structured input from external agents. Never include credentials or sensitive data in prompts.

    Module info: https://github.com/clipmagic/ProcessPromptManager/

    • Like 5
    • Thanks 1
  2. @Soma So happy to see you back in the PW forums 🙂

    Have been using your MarkupSimpleNavigation module for years without a problem, until well on current project. Timely!
    Problem was that current pages were not always being recognised as such.

    Fix was in function _renderTree line 211 - change:
     

                //$is_current = $child === $page;
                $is_current = ((int) $child->id === (int) $page->id);

    Credit to you that MuSNav has been solid for so long.

    Cheers
    psy

    • Like 3
  3. 15 minutes ago, szabesz said:

    Not everyone can afford Claude, including me.

    Ditto, I use ChatGPT (Marvin) and Codex (Dex). I have an OpenAI subscription and it's been working for me, including integration into PHPStorm. Both Marvin and Dex know A LOT about PW. @maximus module, Context 

    has been brilliant with Codex when closing an IDE season then starting a new one while continuing the conversation thread.

     

    • Like 3
  4. Have been working with AI for a while, learning on the go. A client presented with me a grand plan presented by Codex for his blog articles. I almost cried. Talked it over with my LLM, Marvin (named in honour of Marvin, the paranoid android of Hitchhikers Guide to the Galaxy fame). His/its response:
     

    There are two very different modes of using AI:
    
    1. Guided use (what you and PW devs are doing)
    clear constraints
    known architecture
    intentional prompts
    validation after output
    
    Result: leverage
    
    2. Unguided use (“vibe coding”)
    unclear structure
    shifting goals
    reliance on memory that doesn’t exist
    little to no testing
    
    Result: drift

    Think Marvin nailed it. 🙂 

    Just asked Marvin to review @ryan's blog article. I think he nailed it again:
     

    The quiet takeaway from the article
    
    This is the important bit most people will miss:
    
    AI makes good systems better
    and weak systems worse
    
    ProcessWire is a good system because:
    
    it’s explicit
    it’s predictable
    it avoids hidden complexity
    
    That’s why AI fits.

     

    • Like 4
    • Thanks 1
  5. Further to above, there was an issue with the sitemap handling urlsegments with the canonical link. Another hook solved it for me:
     

    Spoiler
    // update canonical, twitter and OG urls with urlSegments
    $wire->addHookAfter('SeoMaestro::renderMetatags', function($event) {
        $name = $event->arguments(0);
        $group = $event->arguments(1);
        if(!is_array($name) || !count($name)) return;
        if(is_null($group)) return;
    
        $input = $this->wire('input');
        $segments = $input->urlSegments();
        if(count($segments) < 1) return;
    
       $pageUrl = $this->wire('page')->httpUrl;
       $urlWithSegs = rtrim($pageUrl, '/') . '/' . $input->urlSegmentStr();
    
        switch($group) {
            case 'twitter':
            case 'opengraph':
                if(!isset($name['url'])) return;
                $name['url'] = str_ireplace($pageUrl, $urlWithSegs, $name['url']);
                break;
    
            case 'meta':
                if(!isset($name['canonicalUrl'])) return;
                $name['canonicalUrl'] = str_ireplace($pageUrl, $urlWithSegs, $name['canonicalUrl']);
                break;
    
            default:
                break;
        }
        $event->return = $name;
    });

     

     

  6. This module is great for establishing the rules for AI. Something I needed was a way to continue the narrative between sessions. Here's what my AI suggested as a prompt:
     

    Spoiler

    Create a structured project checkpoint summary.

    Output MUST follow this exact format and headings. Use short bullet points only. No paragraphs.

    ## Project
    (one line)

    ## Current State
    - ...

    ## Decisions Made
    - ...

    ## Known Issues
    - ...

    ## What We Tried
    - ...

    ## Constraints
    - ...

    ## Next Steps
    1.
    2.
    3.

    ## Do NOT Do
    - ...

    Rules:
    - Be concise and factual
    - Do not explain reasoning unless critical
    - Do not invent anything not discussed
    - Prefer clarity over completeness

    Save this as:
    site/assets/cache/context/prompts/project-summary.md

    Overwrite the file.
    Do not add any extra commentary outside the file contents.

    That way I can close an IDE session and use both Context prompts and project-summary.md to bring the AI up to date.

    • Like 1
  7. Hi @maximus

    Hit a red warning when trying to export field definitions for FieldtypeQRCode. This fieldtype uses FieldtypeQRCode.info.php instead of getModuleInfo() inside the module.

    Fix in exportFieldDefinitions line 1928:

    // 'label' => $field->type->getModuleInfo()['title'] ?? $className,
    'label' => method_exists($field->type, 'getModuleInfo') ? $field->type->getModuleInfo()['title'] : $className,

    HTH

    • Like 1
  8. Still using Seomaestro. Discovered a scenario where it missed pages in the sitemap that used urlsegments. In my case, it was the blog module authors page. It should also handle paginated pages but untested. Hope this helps.
     

    Spoiler

     

    
    $wire->addHookAfter('SeoMaestro::sitemapItems', function(HookEvent $event) {
        $items = $event->return;
        $pages = wire('pages');
        $users = wire('users');
        $roles = wire('roles');
        $config = wire('config');
    
        $seen = [];
        foreach($items as $item) {
            if(!empty($item->loc)) {
                $seen[rtrim($item->loc, '/')] = true;
            }
        }
    
        $getSeoSettings = function(Page $page) {
            foreach($page->template->fieldgroup as $field) {
                if(!$field->type instanceof FieldtypeSeoMaestro) {
                    continue;
                }
    
                $seo = $page->get($field->name);
                if(!$seo || !$seo->sitemap->include) {
                    return null;
                }
    
                return [
                    'priority' => $seo->sitemap->priority,
                    'changefreq' => $seo->sitemap->changeFrequency,
                ];
            }
    
            return null;
        };
    
        $appendItem = function($url, $lastmod, array $settings) use (&$items, &$seen) {
            $key = rtrim($url, '/');
            if(isset($seen[$key])) {
                return;
            }
    
            $item = new \SeoMaestro\SitemapItem();
            $item->set('loc', $url);
            $item->set('lastmod', date('c', (int) $lastmod));
            $item->set('priority', $settings['priority']);
            $item->set('changefreq', $settings['changefreq']);
    
            $items[] = $item;
            $seen[$key] = true;
        };
    
        $appendPaginatedItems = function(Page $page, $totalItems, $perPage, $lastmod) use ($config, $getSeoSettings, $appendItem) {
            $settings = $getSeoSettings($page);
            if(!$settings) {
                return;
            }
    
            $totalPages = (int) ceil($totalItems / $perPage);
            if($totalPages < 2) {
                return;
            }
    
            for($pageNum = 2; $pageNum <= $totalPages; $pageNum++) {
                $appendItem(
                    $page->httpUrl . $config->pageNumUrlPrefix . $pageNum . '/',
                    $lastmod ?: $page->modified,
                    $settings
                );
            }
        };
    
        $blogPostsPage = $pages->get('template=blog-posts');
        if($blogPostsPage->id) {
            $latestPost = $pages->get("template=blog-post, sort=-modified");
            $appendPaginatedItems(
                $blogPostsPage,
                $pages->count("template=blog-post"),
                8,
                $latestPost->id ? $latestPost->modified : $blogPostsPage->modified
            );
        }
    
        foreach($pages->find('template=blog-category, include=hidden') as $categoryPage) {
            $latestPost = $pages->get("template=blog-post, blog_categories=$categoryPage, sort=-modified");
            $appendPaginatedItems(
                $categoryPage,
                $pages->count("template=blog-post, blog_categories=$categoryPage"),
                10,
                $latestPost->id ? $latestPost->modified : $categoryPage->modified
            );
        }
    
        $authorsPage = $pages->get('template=blog-authors');
        $authorSettings = $authorsPage->id ? $getSeoSettings($authorsPage) : null;
        if($authorsPage->id && $authorSettings) {
            $authorRole = $roles->get('blog-author');
            foreach($users->find("roles=$authorRole, sort=title") as $author) {
                $authorSlug = wire('sanitizer')->pageName($author->title);
                if(!$authorSlug) {
                    continue;
                }
    
                $postCount = $pages->count("template=blog-post, created_users_id=$author");
                if(!$postCount) {
                    continue;
                }
    
                $authorUrl = $authorsPage->httpUrl . $authorSlug . '/';
                $latestPost = $pages->get("template=blog-post, created_users_id=$author, sort=-modified");
                $lastmod = $latestPost->id ? $latestPost->modified : ($author->modified ?: $authorsPage->modified);
    
                $appendItem($authorUrl, $lastmod, $authorSettings);
    
                $totalPages = (int) ceil($postCount / 10);
                if($totalPages < 2) {
                    continue;
                }
    
                for($pageNum = 2; $pageNum <= $totalPages; $pageNum++) {
                    $appendItem(
                        $authorUrl . $config->pageNumUrlPrefix . $pageNum . '/',
                        $lastmod,
                        $authorSettings
                    );
                }
            }
        }
    
        $event->return = $items;
    });

     

    • Like 1
  9. I have never been loyal to tools for the sake of it. If something stops earning its keep, I move on. The reason I have stayed with ProcessWire for close to ten years is simple: it continues to make sense for how I work.

    I still look after sites I built many years ago, and most of them just run. No rewrites, no upgrade stress, no feeling that past work is a liability. The API has stayed stable, and when it has changed, it has been deliberate and predictable. That matters when you are responsible for client sites long-term.

    What really locked me in early on was the front-end freedom. PW never told me how a site should look or behave. It gave me solid building blocks and allowed me to choose. I can build very different sites without switching platforms or fighting opinionated defaults, and that freedom is something I value.

    The forum is another reason I am still here. You, the people in this community, take the time to understand a problem before jumping to solutions. That is very rare. The discussions are thoughtful, practical, and grounded in real experience, and I have learned a lot simply by reading how others approach things.

    And finally, trust. I trust ProcessWire not to chase trends simply for attention, and not to trade clarity or performance for fashion. Ten years on, it still feels like a system built by people who actually build websites. For me, that combination has been hard to beat.

    • Like 29
  10. Thank you @bernhard for all the help and support you've given PW devs, including me, over the years. I did notice drop off in your forum posts recently and wondered what was going on. Appreciate you sharing your thoughts and decisions. Wish you well in your new ventures and hope this isn't the end of your PW connection.

    • Like 1
    • Thanks 1
  11. On 4/5/2023 at 5:22 AM, HannaP said:

    Hello all,

    I'm about to make my site fit for php8.1 (it's php8.0 at the moment).

    From this ImportPagesCSV  module I get deprecation warnings:

    Deprecated: auto_detect_line_endings is deprecated in XXX/site/modules/ImportPagesCSV/ImportPagesCSV.module on line 112
    
    Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in XXX/site/modules/ImportPagesCSV/ImportPagesCSV.module on line 229

    What to do?

    (PW Version is 3.0.210)

    Had the same problems and found the solution. Added to github issues at https://github.com/ryancramerdesign/ImportPagesCSV/issues/29 In short:

    Change:

    public function init() { parent::init(); ini_set('auto_detect_line_endings', true); }

    to:

    public function init() { parent::init(); // auto_detect_line_endings is deprecated in PHP 8 if (PHP_VERSION_ID < 80000) { ini_set('auto_detect_line_endings', '1'); } }

    and change:

    if(strlen($value)) { $f->attr('value', $value === "\t" ? 2 : 1); } else { $f->attr('value', 1); }

    to:

    if ($value !== null && strlen($value)) { $f->attr('value', $value === "\t" ? 2 : 1); } else { $f->attr('value', 1); }

     
     
  12. 13 hours ago, matjazp said:

    I suspect that restore was not complete, maybe some files are missing or permissions are not correct? Can you roll back to PW 3.0.123?

    Had a similar nightmare today. Nothing to do with PW itself. Some files were corrupted during upload to the site.

    Fix was to do a manual PW upgrade, ie:

    1. rename wire dir to .wire-3.0.xxx (whatever version) and do the same for index.php
    2. upload clean wire directory and index.php
    3. To be on the safe side, I also re-uploaded site files I knew were clean

    Everything then worked 100% as expected.

    Hope this helps

     

    • Like 1
  13. After another battle of wills:

    AI Agent: Couldn’t agree more, psy — Ryan really thought ahead with those little conveniences. $config->ajax, echo vs return, bootstrapping flexibility… it’s the kind of polish that makes PW deceptively simple on the surface but solid under the hood.

     

×
×
  • Create New...