Repository navigation
Port Linked Orders to the v2 framework and re-base the monorepo on the template - #20
Merged
Merged
Conversation
The root manifests sat on the May scaffold's majors: @wordpress/env 11, @wordpress/scripts 32, @wordpress/e2e-test-utils-playwright 1, jetpack-changelogger 6, and wordpress-configs floating on its default branch. The ranges move to the current majors (@wordpress/env 12.0.0, @wordpress/scripts 37.0.0, which configs requires as a peer, @wordpress/e2e-test-utils-playwright 3.1.0, jetpack-changelogger 7), and configs is pinned to 8d53efd, its current trunk, as the template pins it. Composer also brings PHPStan 2.3.4, WPCS 3.4.1, wp-compat 3 and the WordPress 7.1 stubs through configs. The npm lock was rebuilt from a clean install; the only versions that move down are older copies that hoisting now places at the top of node_modules. Assisted-by: Claude Code:claude-opus-5-5
The three plugin directories were the May scaffold's shells, built on framework types that no longer exist (Core\Kernel\PluginKernel among them), so every plugin lane failed against today's framework. The first port, Linked Orders, is rebuilt from the current plugin template, and Internal Comments and Locked Payment Methods return with their own ports. The shells go, Linked Orders' included. Assisted-by: Claude Code:claude-opus-5-5
The root's workflows were the May scaffold's: actions on floating tags, configs on its default branch, a hand-written plugin list in every matrix, and a split that pushed each mirror's trunk but no tag, so no plugin could release from its mirror. plugins.yml lists the plugin directories, their mirrors (each composer.json's name), those with a readme.txt and each plugin's WordPress branches from its Requires at least up, and the other workflows take their matrices from it, so a plugin joins every lane without an edit. Quality, Tests, Tests (nightly), Supply-Chain Audit, CodeQL and Workflow Checks are the template's at configs 8d53efd, run once per plugin with project-path, plus the readme lint for the plugins with a readme.txt and the changelog check at the root and in every plugin. The split forwards a tag <slug>/vX.Y.Z to that plugin's mirror as vX.Y.Z without --force, as the framework's split forwards v*. Dependabot covers the root and every plugin directory, since the mirrors are read-only; zizmor takes configs' exception for ./ calls. Assisted-by: Claude Code:claude-opus-5-5
The root still described three plugin shells, licensed the monorepo under GPL-2.0-or-later, and kept a shared test environment on WordPress nightly and an unpinned WooCommerce zip, with empty test folders and fan-outs that named the deleted shells. The root is GPL-3.0-or-later, as the framework and template are. The shared environment stays on port 8820, now on the template's pinned WordPress and on WooCommerce from vendor/, with HPOS on and every plugin active, and its smoke spec checks that each plugin it maps is active beside the others. The root runs each plugin's Integration suite in it through test:integration, and the plugins:* fan-outs and the npm aggregators follow the workspace's script names. AGENTS.md, README.md, CONTRIBUTING.md, SECURITY.md and the issue and pull-request templates describe the monorepo as it now works: plugins built from the template's fill-in, releases forwarded to the mirrors, mirrors that take no contributions. Assisted-by: Claude Code:claude-opus-5-5
Linked Orders is the first plugin ported to v2. It starts from wordpress-plugin-template's trunk (d17a381) through the template's own fill-in, run in a clone of the template: name Linked Orders for WooCommerce, slug linked-orders-for-woocommerce, namespace DeepWebSolutions\LinkedOrders, prefix dws_lowc, ports 8822 and 8823, repository ahegyes/wc-linked-orders. As the v1 successor it stays at 2.0.0, which the fill-in sets to 0.1.0 for a new plugin, and its Composer name is its mirror, ahegyes/wc-linked-orders. The directory keeps the template's files but for CONTRIBUTING.md, the pull-request and issue templates, Dependabot's configuration and the fill-in's workflow, script and Quality job, which belong to the monorepo. The template's demo stays until the port replaces it, and every suite passes as filled in. Assisted-by: Claude Code:claude-opus-5-5
Linked Orders carried the template's demo, which a plugin replaces: the greeting feature, the example block and the editor script, with their options, settings fields, tests and build entries. They go as the template's README describes, the greeting's keys, text and validator with them. The build now produces v1's orders list table script, which opens the views of a customer's or a tree's orders in a new tab, and its stylesheet for the order action icons, and the entry's build gate checks the script's asset file. The data consent and the template's generic readers stay until the port replaces them. Assisted-by: Claude Code:claude-opus-5-5
v1 stored three settings under its own keys, read them through dws_lowc_get_*_setting() helpers that other plugins call, filtered them through hooks this port keeps as deprecated, granted four capabilities, and on uninstall removed its order links and options. Nothing in the port carries any of it yet. Rows pin v1's keys and defaults, the section on WooCommerce's Advanced tab under v1's id with v1's two groups, the checkbox and depth round trips, the field and value filters (v1 literal first, then the v2 name), the settings helpers, the migration's defaults, the capabilities on administrators and shop managers, and an uninstall after v1's consent that removes the links on both order storages, v1's leftover rows and the capabilities. The boot row expects the capability grants and the declaration of order-table compatibility. OrderStorage runs a row on either storage, with sync on or off. Assisted-by: Claude Code:claude-opus-5-5
The settings API rows assumed a read without a group found the field in any group, and that both v1 value filters fired on every read. v1 resolved a read without a group only from a field id that names the group, such as general/max-depth, and fired the section's filter only on that path, after the group's. The rows now read the group from the id, expect null for a bare id without a group, and check each path's filters. The settings provider no longer overrides PHPUnit's final groups(), and the partial-deploy row throws from an include that loads after metadata.php, since no include reads the composition root any more. Assisted-by: Claude Code:claude-opus-5-5
The port carried none of what v1 stored or exposed: its settings and their helpers, its capabilities, its order links on uninstall, or its compatibility with WooCommerce's order tables. The settings keep v1's keys and defaults in a section of the Advanced tab under v1's id, with v1's two groups; the yes-or-no selects become checkboxes, which store the same values. Each group's fields pass its v1 filter, deprecated, then its v2 filter. dws_lowc_get_raw_setting(), dws_lowc_get_validated_setting() and dws_lowc_validate_setting() keep v1's signatures and read paths, and the validated value passes v1's filters, deprecated, then dws_lowc/settings/validated_value. The migration stores every default, keeping a v1 site's values. CapabilityGrants gives administrators and shop managers v1's four capabilities. The footprint covers v1's settings, version rows, notices, order-link meta on both storages and the capabilities. The plugin row links to the section and the support forum, and the plugin declares itself compatible with the order tables. Assisted-by: Claude Code:claude-opus-5-5
v1's linking API, its DWS_Order_Node model and its child creation are the plugin's domain surface, which Quote Requests v1 calls, and the port carries none of them yet. Rows link, walk and name orders on both order storages, with sync on and off: the link's stored meta, the tree walk, the root of a child whose parent was deleted, the node's proxy and formatted name, the child-creation check against capability, maximum depth and status, and a created child's customer, addresses, creator and link. Linking refuses a child that has a parent, the order itself, and an ancestor of the parent, because a cycle sends every tree walk into endless recursion. A failed creation returns null or the creator's error, as in v1. Each renamed hook fires its v1 literal, deprecated, then its v2 name. Assisted-by: Claude Code:claude-opus-5-5
The linking rows expected dws_lowc_is_supported_order() to return false for a refund. v1 returns null for anything that is not a WC_Order, and a refund is a WC_Order_Refund, so the row now expects null. Assisted-by: Claude Code:claude-opus-5-5
The port had no linking API, DWS_Order_Node or child creation, which Quote Requests v1 calls and type-hints. includes/post-type.php and includes/linking.php keep v1's twelve helpers with their signatures and return values, and models/ holds DWS_Order_Node on the classmap under its global name, which reads and saves v1's meta keys through WC CRUD, so links work on both order storages. Each v1 hook fires its literal, deprecated, then its v2 name. Linking also refuses the order itself and an ancestor of the parent, which v1 allowed and which sent every tree walk into endless recursion, and a child whose parent was deleted is its own root rather than a fatal error. The node reads the post type from the order's type, so it holds on the order tables too. PHPCS knows WooCommerce's edit_shop_orders capability, and PHPStan covers models/. Assisted-by: Claude Code:claude-opus-5-5
v1 created a linked child from an admin-post action that Quote Requests v1 links to, completed descendants when their parent completed, and numbered a new child through WooCommerce Sequential Order Numbers Pro. The port has none of them. Rows post the action on both order storages and expect the child's editor, a death without a valid nonce, and a one-shot notice with a redirect for a missing parent, a user who may not create a child and a failed creation. Autocompletion rows complete a root on both storages, complete only a child's own subtree, do nothing with the setting off, and honor both filters, whose v1 literals fire first. The SOS Pro integration loads only beside that plugin, which a stand-in function replaces in a separate process. Assisted-by: Claude Code:claude-opus-5-5
The autocompletion rows expected the note to read only "Child Order autocompleted.", but WooCommerce appends its own sentence on the status change to a transition's note. The boot row listed only the components the port had then. The note row expects the whole note, and the boot row expects child creation and autocompletion loaded and the Sequential Order Numbers Pro integration skipped, since the tests site runs without that plugin. Assisted-by: Claude Code:claude-opus-5-5
The port had no admin-post action to create a linked child, no autocompletion of descendants and no Sequential Order Numbers Pro integration, all of which v1 shipped. ChildCreation answers v1's admin-post action and nonce, opens the new child in its editor on either order storage, and otherwise returns to the referring screen, or the parent, with a one-shot user notice through AdminNotices. Autocompletion checks the setting when an order completes, as v1's validated read did, and walks the order's descendants with v1's filters, deprecated, before the v2 names. The integration loads only where wc_seq_order_number_pro() exists and numbers a child from the v2 creation hook; a stand-in in tests/Fixtures gives PHPStan the plugin's API. dws_lowc_get_singular_name() holds the order type's label lookup, which the node now shares. Assisted-by: Claude Code:claude-opus-5-5
v1 showed an order's links in a meta box and added a depth column, depth, guest and tree filters and three row actions to the orders list, all on the posts table only. The port shows none of them. Meta box rows register the box beside an order's editor on both storages, for users with its capabilities only, and render the parent, the children, the add-child button with its nonce, and the message for an order at the maximum depth. List rows check the column and its labels, the depth filter up to the deepest stored or allowed level, the queries by depth, guest email and tree, the row actions and their URLs on either list, and the script and icons only on order lists. The three hooks they fire keep their v1 literals first. Assisted-by: Claude Code:claude-opus-5-5
The meta box rows called add_meta_box(), which WordPress loads only in admin requests along with the screen API it needs, and the boot row did not list the new components. The depth-filter row created its deepest order last, so a lookup that ignored depth and took the newest order passed too. The rows load template.php and the screen API as an admin request does, the boot row expects the meta box and the list table, and the depth-filter row adds a newer, shallower order, which fails both posts-storage rows when the lookup sends that store a meta query. Assisted-by: Claude Code:claude-opus-5-5
v1's meta box and list additions hooked the posts table's screens, queries and links only, so on a store using WooCommerce's order tables none of them appeared. MetaBox registers on add_meta_boxes for whichever editor screen fires it and links through get_edit_order_url(). ListTable hooks the depth column from current_screen into either list, the filters into both restrict hooks, and the depth, guest and tree filters into the posts request and the order-table query arguments, with the guest email as a meta clause or billing_email as each store takes it. The row actions link to either list. The deepest stored level comes from one order query per store, since the posts store refuses a meta query. Capabilities are checked in each callback. Each v1 hook fires its literal, deprecated, then its v2 name. dws_lowc_get_max_depth() now reads the validated setting, so v1's filters apply to every depth check, and dws_lowc_get_order_node() documents that it takes any value. Assisted-by: Claude Code:claude-opus-5-5
v1 exposed five getters, two hook-tag builders and five constants at the root, and fired dws_lowc_initialized and dws_lowc_initialization_failure after its boot. The port defines none of them, so code that still calls them fatals. Rows expect the getters to answer from the plugin header, each with a deprecation naming its replacement where it has one, the constants to hold the same values, the hook-tag builders to join and sanitize names as v1 did, with null for a component v1 did not have, and the boot hooks to fire, deprecated, after the framework's, with the failure. Assisted-by: Claude Code:claude-opus-5-5
Code written against v1 called dws_lowc_name() and its siblings, read the DWS_LOWC_* constants, built hook names with the hook-tag helpers, and listened for dws_lowc_initialized; under the port each of these fatals or never fires. includes/deprecated.php defines the five constants from the plugin header, read once per request, so they follow each release; v1's whitelabel prefix on the name goes. The five getters answer from the same values, and the two builders join and sanitize names as v1 did, mapping its twelve component ids to their roots, each raising a deprecation. functions.php hooks the two v1 boot hooks, deprecated, to the framework's succeeded and failed hooks, passing the failure's Throwable where v1 passed its own exception. Assisted-by: Claude Code:claude-opus-5-5
Linked Orders ships through wordpress.org, whose readme.txt states the stable tag and floors a second time, and the changelog sync fills its changelog block between two markers. The port has no readme.txt. Unit rows expect the readme's Stable tag, Requires at least and Requires PHP to equal the entry file's Version and floors, and its changelog block to hold the markers. Assisted-by: Claude Code:claude-opus-5-5
Linked Orders ships from wordpress.org, as v1 did, but the port still carried the template's GitHub distribution: an Update URI, its own text-domain loading, the template's description, no readme.txt and a release that publishes to GitHub only. The entry file drops both and takes v1's description, which turns the bundled updater inert and leaves translations to language packs. readme.txt keeps v1's sections updated for v2, with an Upgrade Notice naming the floors and the removed hooks. CHANGELOG.md holds v1's history between the markers that changelog:write copies into the readme, through the Pronamic sync it now runs, and changelog/ stages the 2.0.0 entries, one per change the rulings list. The plugin's quality workflow lints the readme, and its release passes wp-org. Assisted-by: Claude Code:claude-opus-5-5
The Integration rows fire the hooks the meta box and the orders list answer, but never load those screens, so a hook WooCommerce fires at another time, or not at all, would pass them. An end-to-end spec creates an order, adds a child from its meta box, expects the child's editor to name the parent, and expects the orders list to show both orders' depth; a second follows the plugin row's Settings link to the section. The tests environment stores orders in WooCommerce's order tables, so these cover that storage, and the Integration rows cover the posts table. Assisted-by: Claude Code:claude-opus-5-5
Ruling 46 asks for each Integration row that touches orders on the posts table and on WooCommerce's order tables, each with the sync between them on and off, but nineteen rows ran only on the tests site's own storage, the order tables without sync. Those rows take the storages provider. The list-table rows also clear the column hooks a screen adds, which stayed for the rest of the process and showed the depth column to a user without its capability on the next posts-table row. The rows that check hook order, and the stand-in plugin's row, stay on one storage, since the storage changes neither. Assisted-by: Claude Code:claude-opus-5-5
The plugin's README, AGENTS.md, architecture and tests docs, SECURITY scope and manifest descriptions still described the template: its demo, its fill-in, its blocks and its GitHub distribution. docs/ recipes.md, the template's guide, is removed in 846f4f4, which staged it by accident. Each now describes Linked Orders: what it does and how other plugins extend it, its layout of components, v1 API and model, its order storage and v1-hook conventions, its data under v1's keys and its wordpress.org distribution. They point at the template's recipes, which it maintains. The empty templates/ folder goes, and so do PHPCS's exclusion of a blocks build and two workflow comments that named the fill-in. Assisted-by: Claude Code:claude-opus-5-5
The Claude review found four gaps the rows missed. The uninstall rows ran with the plugin loaded, while WordPress loads only uninstall.php for an inactive plugin. Linking accepted a root that had children, whose descendants then kept their old depths. The child-creation action read its parent id with absint(), so '-42' named order 42. The tree filter replaced the orders a search had found instead of narrowing them. Rows uninstall through a WP-CLI request that skips the plugin, refuse a child that has children, treat a negative or partly numeric parent id as no order, and expect the tree filter to keep only the found orders in the tree, on both storages. Assisted-by: Claude Code:claude-opus-5-5
The row expecting a malformed parent id to name no order left the children it created behind while the action still read the id with absint(). The shared tests site then held orders on the order tables without posts copies, and the next start of the site failed WooCommerce's sync check before enabling the order tables. The row now hands every child of the parent to the cleanup before asserting there are none. Assisted-by: Claude Code:claude-opus-5-5
An uninstall loaded footprint.php without the capability constants it names, so every real uninstall, where WordPress loads uninstall.php alone, failed and kept the plugin's data despite consent. Linking a root that had children left its descendants at their old depths, past the maximum. The child-creation action read its parent id with absint(), so '-42' named order 42. The tree filter replaced the orders a search had found. uninstall.php loads capabilities.php. A child must be a lone root, neither with a parent nor with children, which also rules out every ancestor, so the root lookup goes. The parent id is read as a whole number of at least 1. The tree filter intersects what a search found on either storage. A node loads its parent's and children's orders only when asked, so the depth column no longer loads them for every row. dws_lowc_get_create_child_url() builds the add-child URL for the meta box and the row action. The typed option readers, which nothing called, go with their rows. Assisted-by: Claude Code:claude-opus-5-5
Linking now refuses a root that has children, which v1 accepted, and code that linked whole trees through dws_lowc_link_orders() gets false where v1 gave true. The 2.0.0 changelog did not say so. A major changed entry names the refusal and why. Assisted-by: Claude Code:claude-opus-5-5
Codex found three gaps. A v1 filter on the validated depth could stop new children with 0, which the port clamped back to 1. The depth and guest filters joined another extension's OR meta query as further alternatives. The include loader's underscore check asserted on a file the port never had. Rows expect a filtered depth of 0 to stand, the depth filter to narrow an OR meta query on both storages, and a fresh request to skip an underscore-prefixed include written for the row. The uninstall row without the plugin restores the site through a request with the plugin, as the site's next request would: its in-process restore left the migration runner unable to run later in the same process, which failed the migration rows in the shared environment's order. Assisted-by: Claude Code:claude-opus-5-5
The depth reader clamped v1's filtered value back to at least 1, so a filter returning 0 no longer stopped new children as it did in v1. The list's depth and guest clauses joined an OR meta query already in place as further alternatives, broadening the list. CI ran each plugin's suites in its own directory and never the root's shared environment, so a broken root mapping or activation would pass. The docs claimed four storages for every order-touching row, and the Upgrade Notice left out the removed v1 accessors. dws_lowc_get_max_depth() returns the filtered value; the stored value keeps its minimum of 1 through validation. The list nests the query in place and its own clauses under one AND. A root composer script, test:integration:ci, installs and builds the plugins before the shared Integration suite, which tests.yml now runs beside the shared end-to-end suite. The docs name the rows that use one storage, and the Upgrade Notice names the removed accessors. Assisted-by: Claude Code:claude-opus-5-5
Codex found two gaps. v1 read any field of a known group from its option, such as one an extension adds through the group's field filter, which the port keeps, but the settings API reads only the three built-in fields. The Sequential Order Numbers Pro row named the number set before the child's save without checking it. A row expects an extension field to read back through the raw and validated getters, by group and through the section, and an unknown group to read as null. The stand-in numbers the order on its object, and the row expects the reloaded child to hold that number. Assisted-by: Claude Code:claude-opus-5-5
The settings API resolved only the three built-in fields, returning
null for any other, while v1 read every field of a known group from
its option and the port keeps the group filter through which an
extension adds one. Such a field saved through WooCommerce but read
back as null.
dws_lowc_find_setting() derives the option, dws-lowc_{group}_{field},
for any field of a known group, and validation stays specific to the
built-in settings. DWS_LOWC_SETTINGS_GROUPS lists v1's groups only,
since the option follows from the group and the field.
Assisted-by: Claude Code:claude-opus-5-5
Codex found two gaps. The depth filter's deepest level skipped trashed orders, which v1's query counted and the list's Trash view shows. The meta box rows passed a WC_Order on every storage, while the posts editor passes its WP_Post, so a callback narrowed to orders would pass them and break that editor. A row expects a trashed order's depth at the deepest level, on all four storages, and the meta box rows pass the post on the posts storage and the order on the order tables. Assisted-by: Claude Code:claude-opus-5-5
The depth filter's deepest level came from an order query without a status, which WooCommerce limits to its live statuses, so an order in the Trash, which the list's Trash view shows, never set it, though v1's query counted every order of the type. The query names every order status and trash. Assisted-by: Claude Code:claude-opus-5-5
Codex found that autocompletion skipped the completed order itself, while v1 walked the order's whole tree, the order first, through both filters, so a v1 completed_status filter could set the order itself to another status. A row on all four storages expects the completed order to pass the should_autocomplete filter before its child. The deprecated-hook rows complete a lone order, whose tree is the order alone, so each filter still runs once. Assisted-by: Claude Code:claude-opus-5-5
Autocompletion skipped the order that completed and walked only its descendants, while v1 walked the order's whole tree, the order first, through both filters, so a v1 completed_status filter could no longer set the order itself to another status. The walk starts at the order again. Setting an already completed order to completed changes nothing, so the order changes only through a filter. Assisted-by: Claude Code:claude-opus-5-5
Codex found two gaps. The deepest-level query named WooCommerce's order statuses and trash, so an order in any other registered status, such as a draft or another plugin's status, never set the level, though v1 counted every order of the type. With no filter chosen, the list's query callbacks still added a meta_query key, and WooCommerce counts the order tables' list from its cache only when the query holds none of its own arguments, so every unfiltered list view ran a count query. A row on all four storages expects a trashed and then a draft order to set the deepest level, and another expects an unfiltered list's query to come back as it went in. Assisted-by: Claude Code:claude-opus-5-5
WooCommerce sets a draft order back to pending on save, so the deepest-level row's draft case never held the status it named. v1 Quote Requests registers its quote statuses only as post statuses, outside WooCommerce's order statuses, which is the case the row means. The row registers such a status, accepts it on the order while it saves, and expects the order holding it to set the deepest level. Assisted-by: Claude Code:claude-opus-5-5
The deepest-level query named WooCommerce's order statuses and trash, so a supported order type whose statuses are registered only as post statuses, as v1 Quote Requests registers its quotes', never set the level, though v1 counted every order of the type. The list's query callbacks added a meta query key even with no filter chosen, which kept WooCommerce from counting the order tables' list from its cache on every unfiltered view. The query names every registered post status, and the callbacks add a meta query only when a depth or guest filter is chosen. Assisted-by: Claude Code:claude-opus-5-5
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
The wordpress.org readme linter fails CI on wordpress.org's default readme text, and the manual installation step, kept from v1, matched its "Upload ... plugin ... directory" pattern. The step says to copy the unzipped folder into the site's wp-content/plugins folder, which no default-text pattern matches. Assisted-by: Claude Code:claude-opus-5-5
A parent's children are one meta value that every link reads and rewrites. A request that loaded the parent before another request linked a child to it wrote back its own list, dropping that child, as two clicks on the add-child button can do. v1 shares the race. A row on all four storages links one child in a second WP-CLI request after this request loaded the parent, links another here, and expects both listed. Others expect a link to be refused while another request holds the linking lock, and a linked child whose link is refused to be deleted and an error returned in place of its id. Assisted-by: Claude Code:claude-opus-5-5
Every link read the parent's children list, added one and wrote the list back, so two requests linking to one parent at the same time, as two clicks on the add-child button can, each wrote their own list and one child dropped out of it while keeping its own parent. A request that loaded the parent before the other request's link dropped the child even without overlapping, since it wrote back the meta it had cached. dws_lowc_link_orders() holds the framework's Storage\Lock, as the dws_lowc_linking option, while it links, re-reads both orders' meta from the database once it holds it, and returns false while another request holds it. dws_lowc_create_linked_child() no longer ignores a refused link: it deletes the child it created and returns a WP_Error, which the add-child action reports. Assisted-by: Claude Code:claude-opus-5-5
Codex found that WooCommerce's order-tables data caching, off by default but recommended for stores with an object cache, keeps each order's meta for the request, and read_meta_data( true ) reads through that cache, so a link still wrote back a parent loaded before another request's link and dropped its child. The lost-update row gains a variant on the order tables with data caching on. Assisted-by: Claude Code:claude-opus-5-5
With WooCommerce's order-tables data caching on, read_meta_data( true ) read each order's meta from the cache this request filled when it first loaded the order, so a link under the lock still wrote back a parent loaded before another request's link and dropped its child. The link clears both orders' cached data through the order data store before it re-reads their meta; the posts store has no such cache, and the call reaches nothing there. Assisted-by: Claude Code:claude-opus-5-5
The shared environment's Integration job failed the row that brings a new site to revision one. The migration runner's 20-second budget lasts for the request, and in a fresh site the activating request does not boot the plugin, so PHPUnit's bootstrap is the first request that migrates and starts the budget. The row's in-process run, minutes into the suite, found it spent and only scheduled a continuation. A local site that had already migrated never started the budget, so the row passed there. The row runs in a separate process, as a site's next request would. Assisted-by: Claude Code:claude-opus-5-5
v1 Quote Requests resets a quote row's actions at priority 999, keeping Linked Orders' only where its settings allow quote linking. v1 Linked Orders added its actions at the same priority but booted first, so the reset saw them. v2 boots on plugins_loaded at 15, after Quote Requests v1 has registered, so its actions land after the reset and show on quote rows the settings hide them from. A row registers a reset at 999 before the plugin's own callback and expects the reset to have the last word. Assisted-by: Claude Code:claude-opus-5-5
v1 Quote Requests resets a quote row's actions at priority 999 and keeps Linked Orders' only where its settings allow quote linking. v2 added its actions at the same priority, after Quote Requests v1 had registered, so they survived the reset and showed on quote rows. The actions go in at 998, so the reset has the last word whichever plugin boots first. Assisted-by: Claude Code:claude-opus-5-5
No row failed when a link skipped re-reading the child's meta, though a child another request linked after this one loaded it was then linked again, ending with two parents. The rows also left the order tables' data caching to whatever the tests site stored. A row on all five variants expects that link to be refused and the child to keep its first parent; it fails on each when the child's forced read is removed. OrderStorage pins the data caching option for every row, and the lost-update row checks the parent as this request sees it after the saves as well as after a cache flush. Assisted-by: Claude Code:claude-opus-5-5
A link cleared both orders' cached data through the order data store's clear_cached_data(), which WooCommerce tags @internal, reached through WC_Data_Store's magic forwarding and a PHPStan ignore. The bypass is needed only with the order tables' data caching on, since that cache answers read_meta_data( true ) in place of the database. The link turns the caching off through the option's pre_option filter for the two forced reads only, and removes the filter before the saves, which then clear WooCommerce's cache as they do without it. Assisted-by: Claude Code:claude-opus-5-5
A link refused at once while another request held the linking lock, though v1 callers such as Quote Requests never check whether a link worked, so an order created from an accepted quote could stay unlinked when two links overlapped by a few milliseconds. A row has a second request hold the lock for about a second and expects the link to wait for it and succeed; the refusal row expects the link to give up only after five seconds. Assisted-by: Claude Code:claude-opus-5-5
PHPCS warned that the lock-row reader's assignment in the wait row was out of line with the counter below it. Assisted-by: Claude Code:claude-opus-5-5
A link refused at once while another request held the linking lock, though a link holds it for milliseconds and v1 callers such as Quote Requests never check whether a link worked, so an order created from an accepted quote could stay unlinked. dws_lowc_link_orders() retries the lock every 50 milliseconds for up to five seconds, and refuses only after that, as when a request died holding it until its 30-second hold expires. An overlapping second click on the add-child button now links its child too, as in v1. Assisted-by: Claude Code:claude-opus-5-5
Every Deep Web Solutions plugin moves to v2 together, so Linked Orders need not order its row actions around v1 Quote Requests' reset, and comments that justified code by v1 Quote Requests or by v1 callers would go stale once those are gone. The row actions go back to priority 999 and the row that registered a reset before them goes. The link's docblock gives the general reason it waits for the lock, and the deepest-level row names its custom status without referring to quotes. Assisted-by: Claude Code:claude-opus-5-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports Linked Orders for WooCommerce 1.2.2 to the v2 framework as 2.0.0, on both order storages and distributed through wordpress.org, and brings the monorepo root, its CI and its shared test environment up to the plugin template's conventions, removing the three pre-re-architecture plugin shells.
Type of change
Checklist
composer changelog:addin the changed plugin's directory, or at the root for the monorepo itself)composer quality-checkpasses locally in every changed pluginreadme.txtupdated if runtime requirements changedWhat changes
plugins.ymlas the matrix source (plugins, projects, readmes, mirrors and WordPress branches from each plugin's floor).split-plugins.ymlpushes trunk to every mirror and a<slug>/vX.Y.Ztag to its mirror asvX.Y.Z, without--force. Dependabot covers/and/plugins/*. The shared tests environment, on port 8820, maps Linked Orders, and CI runs its Integration and end-to-end suites through the root'stest:integration:ciandsetup. The license is GPL-3.0-or-later.DeepWebSolutions\LinkedOrders\Scoped. v1's option and meta keys, settings section and groups, settings API, capabilities, linking API,DWS_Order_Node, add-child action, meta box, orders list additions, autocompletion and Sequential Order Numbers Pro integration work on the posts storage and the order tables, with and without sync, and the plugin declares compatibility with the order tables. Every v1 hook fires deprecated before itsdws_lowc/name; v1's root getters, hook-tag builders, constants and boot hooks stay, deprecated.Breaking changes
dws_lowc/names; their v1 names still fire, deprecated, but for eight settings hooks and the v1 framework's notice-markup hooks, which are removed, as are the v1 framework's accessors such asdws_lowc_instance()anddws_lowc_component().dws_lowc_link_orders()returns false unless the child is another order with neither a parent nor children. v1 accepted the order itself and its ancestors, which made cycles, and roots with children, whose descendants kept depths past the maximum.dws_lowc_link_orders()waits up to five seconds for another request's link and returns false only after that;dws_lowc_create_linked_child()then deletes the child it created and returns aWP_Error, which the add-child button reports.Not verified locally
wc-linked-ordersmirror needs awp-org-releaseenvironment withSVN_USERNAMEandSVN_PASSWORDbefore the first wordpress.org release.