Skip to content

upstream#15

Merged
willymwai merged 26 commits intotruehostcloud:masterfrom
plasmicapp:master
May 22, 2025
Merged

upstream#15
willymwai merged 26 commits intotruehostcloud:masterfrom
plasmicapp:master

Conversation

@willymwai
Copy link
Member

@willymwai willymwai commented May 22, 2025

PR Type

Enhancement, Bug fix, Tests, Documentation


Description

  • Add and integrate new GraphQL fetch package (@plasmicpkgs/graphql)

    • Implements fetchGraphQL function and registration for Plasmic hostless
    • Updates monorepo and dependencies to include new package
  • Enhance and refactor SVG optimization logic

    • Migrate to SVGO v3 API, simplify plugin handling, and improve dimension extraction
    • Add missing xmlns handling for SVGs
  • Improve CMS SQL query logic and validation

    • Refactor makeSqlCondition for clarity and error handling
    • Enforce non-negative limit/offset in CMS queries, with error reporting
    • Add comprehensive tests for SQL condition generation and CMS querying
  • Add and test CSS box-shadow parsing utility

    • Implement parseBoxShadows for robust box-shadow normalization
    • Integrate into HTML/CSS value fixers and add unit tests
  • Miscellaneous enhancements and fixes

    • Add dev app host (plasmicpkgs-dev) for local package testing
    • Improve code component registration for dev environments
    • Update dependencies (SVGO, css-tree, React Aria/Stately, etc.)
    • Fix server query codegen to use valid JS identifiers
    • Various test and doc updates

Changes walkthrough 📝

Relevant files
Enhancement
21 files
index.ts
Add GraphQL fetch function and registration                           
+76/-0   
svgo.ts
Refactor SVG optimization to SVGO v3, improve dimension handling
+149/-241
cms-util.ts
Refactor SQL condition builder, improve error handling     
+113/-70
css-utils.ts
Add box-shadow parsing utility                                                     
+90/-0   
html-parser.ts
Integrate box-shadow parsing into CSS value fixer               
+10/-32 
DataPickerUtil.ts
Add utility to extract expected values from prop types     
+12/-0   
PropEditorRow.tsx
Use extracted expected values for data picker                       
+2/-1     
CopilotPromptDialog.tsx
Pass style tokens to Copilot UI requests                                 
+7/-0     
ApiSchema.ts
Add CopilotToken type and tokens to Copilot UI request     
+8/-0     
prompt-utils.ts
Support Copilot tokens in prompt utils                                     
+2/-1     
cms.ts
Refactor unique fields data extraction logic                         
+16/-19 
CmsEntryDetails.tsx
Use unified unique fields data extraction                               
+4/-5     
index.ts
Refactor fetch error handling and response shape                 
+17/-102
components.tsx
Add min:0 to limit/offset props in CMS query repeater       
+4/-2     
misc.ts
Make S3 clip bucket/endpoint configurable via env               
+5/-3     
page.tsx
Add catchall page for dynamic Plasmic rendering                   
+68/-0   
layout.tsx
Add root layout for dev app host                                                 
+11/-0   
page.tsx
Add Plasmic Canvas Host endpoint for dev                                 
+6/-0     
plasmic-init.ts
Add Plasmic loader initialization for dev host                     
+25/-0   
plasmic-init-client.tsx
Add client-side Plasmic provider and registration logic   
+78/-0   
plasmic-register-dev-meta.ts
Add dev meta registration utility for code components       
+158/-0 
Dependencies
4 files
package.json
Add package manifest for @plasmicpkgs/graphql                       
+42/-0   
package.json
Update dependencies (SVGO v3, css-tree, etc.)                       
+5/-4     
package.json
Update fetch package version and metadata                               
+10/-5   
package.json
Bump version for CMS package                                                         
+1/-1     
Documentation
4 files
index.api.md
Add API report for GraphQL package                                             
+26/-0   
index.api.md
Update fetch API report for new response shape                     
+1/-28   
README.md
Add documentation for dev app host                                             
+21/-0   
README.md
Add root documentation for plasmicpkgs                                     
+15/-0   
Tests
4 files
cms-util.spec.ts
Add comprehensive tests for SQL condition builder               
+477/-2 
DbMgr.spec.ts
Add tests for CMS query limit/offset validation                   
+78/-23 
css-utils.spec.ts
Add tests for box-shadow parsing utility                                 
+78/-0   
html-parser.spec.ts
Add tests for box-shadow parsing in CSS fixer                       
+37/-2   
Bug fix
6 files
DbMgr.ts
Enforce non-negative limit/offset in CMS queries                 
+12/-8   
paste.tsx
Reorder paste priority: HTML after image                                 
+5/-5     
serializer.ts
Fix server query codegen to use valid JS identifiers         
+4/-2     
clipboard-test-data.ts
Fix SVG data URI for test consistency                                       
+1/-1     
@react-aria+focus+3.20.3.patch
Patch React Aria FocusScope to fix null error                       
+18/-0   
@react-aria+overlays+3.27.1.patch
Patch React Aria overlays for overlay positioning               
+4/-2     
Configuration changes
6 files
package.json
Register new GraphQL package and dev host in monorepo       
+3/-1     
package.json
Add package manifest for dev app host                                       
+25/-0   
tsconfig.json
Add TypeScript config for dev app host                                     
+27/-0   
next.config.js
Add Next.js config for dev app host                                           
+6/-0     
tsconfig.json
Add TypeScript config for GraphQL package                               
+7/-0     
.prettierignore
Ignore generated api/ folders in plasmicpkgs                         
+1/-0     
Additional files
7 files
page.tsx +2/-1     
.env.example +2/-0     
package.json +2/-2     
package.json +2/-2     
auto-open.spec.ts +1/-1     
react-hook-spec.ts +0/-6     
styles.ts +0/-8     

Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • asimkhan73301 and others added 26 commits May 19, 2025 16:20
    …s after image paste (#1063)
    
    GitOrigin-RevId: e5132dcff0399a3d5d14ffc9c8816fb8171debde
    Change-Id: I2f6a7aa6fc3a01cbf1c513a1752157822d66029c
    GitOrigin-RevId: 5f7a070589356d0441120d92a7e7861ed96f53a5
    GitOrigin-RevId: 220ab648e419a7ea3edd144c0c5fdd3c64c6fe47
    - improve overall structure of makeSqlCondition for consistency
    - handle _id outside top-level
    - handle field = false outside top-level
    - improve error handling
    
    Change-Id: Ie9ea389dcbd67714456facd521b3ceb0b6bc8e67
    GitOrigin-RevId: 37ab5ce5f252754235772d3c9b4703c06da3dce8
    Change-Id: I244289230ef1c71a35140320184f6badaa1a579f
    GitOrigin-RevId: f1b2e8a0e2c43adb28da295e586b20036908f0c3
    GitOrigin-RevId: 7b1c16bc5ef9d3b20423dd9acef87de1bc6a91f8
    …ifferent environments
    
    GitOrigin-RevId: 9132205b7ce91e76e453fe420be44e7785f96dc8
    Change-Id: I4f409d37bbeccf9c6b3c4c750952cc71681b1b6c
    GitOrigin-RevId: 42032f14bef606d0615a23d291e81611c3d08145
     - @plasmicpkgs/plasmic-cms@0.0.282
    
    GitOrigin-RevId: a97331f2ebfce8d9ad917662c5ffd8cadc672304
    GitOrigin-RevId: 3d152328f1e0bda1f37039c253ad2e0187958fa1
    We were not checking if a field is a unique or not.
    This did not cause any issues since it still looked for null fields properly.
    Simplify this code by combining into getUniqueFieldsData.
    
    Change-Id: I6389849c1fbb9538c959ddc35a7ce8b22098f455
    GitOrigin-RevId: 28ce7d53e64f063017259fa37b4d342e6e0c0602
    Change-Id: I9ade8606108c79dc705dede6787aa7199353ba90
    GitOrigin-RevId: a5d1d0d0fcc2d0357849b4676f3ade4af033383a
     - plasmicpkgs-dev@0.0.2
    
    GitOrigin-RevId: 19d69fa8220dd79fa85b2b2d8ae60930d5bdd4fb
    * fix(svgo): upgrade package version to fix svg import issues
    
    * tests: fix dataUri in tests
    
    GitOrigin-RevId: bfe8027df1d4f9c333eb970e56521ffc3b1d00eb
    * add repo info, add devDep, delete yarn.lock
    * remove GraphQL from fetch
    * simplify fetch, rename symbols
      response -> body
      CustomError -> HttpError
      remove FetchProps
    * add separate @plasmicpkgs/graphql package
    * register fetch, graphql in plasmicpkgs-dev
    * prettierignore API docs
    
    GitOrigin-RevId: 4b9dcd30fccf4396b3f372bd1d72f80b52817ce2
     - plasmicpkgs-dev@0.0.3
     - @plasmicpkgs/fetch@0.0.6
     - @plasmicpkgs/graphql@0.0.2
    
    GitOrigin-RevId: 3ca2cc1765db2722ee22e4e0ed1e91a48b2a49de
    * Add publishConfig back for fetch
    * Add publishConfig to graphql, reset to 0.0.0
    
    GitOrigin-RevId: 3d4515b0f69ce6ad0b4aad9c885835680865bf36
     - plasmicpkgs-dev@0.0.4
     - @plasmicpkgs/fetch@0.0.7
     - @plasmicpkgs/graphql@0.0.1
    
    GitOrigin-RevId: 99eedda0d26a21d13b5b6887f85d3c1f34740760
    GitOrigin-RevId: 3db3017ef52dbcecc7e638f60dd6687dfea470e6
    * fix(copilot): fixed parsing of box-shadow in html-parser
    
    * feat(copilot): reformat ui copilot prompt with better organization and box shadow guidelines
    
    * fix(copilot): parse multiple box shadows
    
    * chore(copilot): improve the ui copilot prompt to use shadows when necessary
    
    * feat(web-importer): parse box-shadow using css tree AST instead of regex
    
    * chore(copilot): minor prompt changes in box-shadow guidelines
    
    * chore(copilot): minor requested changes for code organization in css-utils
    
    * fix(copilot): change default box-shadow color to be currentcolor
    
    * chore(copilot): remove shadow plasmic related rules as per improvements in html parser
    
    * fix(copilot): parse rgb color properly
    
    * chore: add yarn.lock changes after rebase
    
    GitOrigin-RevId: f47cd03565734a8ce59d33a029cf066ef591ef29
    …E (#1072)
    
    * feat(copilot-tokens): fix token vars support in html parser and add support of tokens in ui copilot FE
    
    * chore(copilot-tokens): add test cases for tokens support in background property
    
    GitOrigin-RevId: 366bdb638b08ce62584d882ac4ccd3815000a2cb
    * feat(copilot-backend): add existing tokens context in ui copilot prompt
    
    * chore(copilot-tokens): add comments for token context prompt
    
    * chore(copilot-tokens): use [] instead of Array for types
    
    GitOrigin-RevId: ae165de1cb0a5a6d3eb999ef3de52453de97addb
    Change-Id: I8974af9cd3b78c4c7ae1191ebd9574e30a2d3c74
    GitOrigin-RevId: 858676f84571105ae3d9e2bdaa23d04ae405e5fb
    GitOrigin-RevId: 4681aa4415f0d99c959a8b328375ff1ab3475a13
    …electors (#1079)
    
    GitOrigin-RevId: 012c137968599d0e4ab3d2dac99ffc3729b988ce
    GitOrigin-RevId: edca5894d9c195f59d158b2d7f84f4ebbbed67be
    @willymwai willymwai self-assigned this May 22, 2025
    @qodo-code-review
    Copy link

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
    🧪 PR contains tests
    🔒 Security concerns

    SQL injection:
    The makeSqlCondition function in cms-util.ts builds SQL queries from user input. While it does use parameterized queries with the getValParam function, complex conditions are constructed by concatenating strings. Verify that all user inputs (field names, operators, etc.) are properly validated before being included in SQL statements, especially in the makeTypedFieldSql function where field paths are directly inserted into SQL strings.

    ⚡ Recommended focus areas for review

    SVG Processing Refactor

    The SVG optimization logic has been completely refactored to use SVGO v3 API. Verify that all edge cases are handled correctly, especially the dimension extraction and xmlns handling which are critical for proper SVG rendering.

      },
    };
    
    const createDimensionsExtractor = () => {
      const dimensions: { width?: number; height?: number; aspectRatio?: number } =
        {};
      const plugin = {
        type: "visitor",
        name: "extract-dimensions",
        fn() {
          return {
            element: {
              // Node, parentNode
              enter({ name, attributes }, { type }) {
                if (name === "svg" && type === "root") {
                  if (
                    attributes.width !== undefined &&
                    attributes.height !== undefined
                  ) {
                    dimensions.width = Number.parseFloat(attributes.width);
                    dimensions.height = Number.parseFloat(attributes.height);
                  } else if (attributes.viewBox !== undefined) {
                    const viewBox = attributes.viewBox.split(/,\s*|\s+/);
                    dimensions.width = Number.parseFloat(viewBox[2]);
                    dimensions.height = Number.parseFloat(viewBox[3]);
                    const ratio = dimensions.width / dimensions.height;
                    dimensions.aspectRatio =
                      ratio && isFinite(ratio) ? ratio : undefined;
                  }
                }
              },
            },
          };
        },
      };
    
      return { dimensions, plugin };
    };
    SQL Injection Risk

    The refactored SQL condition builder handles user input for database queries. Ensure all user inputs are properly parameterized and that the query construction logic prevents SQL injection attacks.

    export const makeTypedFieldSql = (
      field: string,
      fieldMetaMap: FieldMetaMap,
      opts: { useDraft?: boolean }
    ) => {
      const dataRef = makeDataRef(opts);
      if (field === "_id") {
        return "r.id";
      }
    
      // Simple field access
      const meta = fieldMetaMap[field];
      if (meta) {
        return `(${dataRef}->''->>'${meta.identifier}')::${typeToPgType(
          meta.type
        )}`;
      }
    
      // TODO: handle more than one level of nesting
      // TODO: allow escaping "." in field identifier
      if (field.includes(".")) {
        const [objectField, nestedField] = field.split(".");
    
        const objectFieldMeta = fieldMetaMap[objectField];
        if (objectFieldMeta && objectFieldMeta.type === CmsMetaType.OBJECT) {
          const nestedFieldMeta = objectFieldMeta.fields.find(
            (f) => f.identifier === nestedField
          );
    
          if (nestedFieldMeta) {
            return `(${dataRef}->''->'${objectFieldMeta.identifier}'->>'${
              nestedFieldMeta.identifier
            }')::${typeToPgType(nestedFieldMeta.type)}`;
          }
        }
      }
    
      return null;
    };
    CSS Parsing Edge Cases

    The new box-shadow parsing utility handles complex CSS values. Verify it correctly handles all edge cases including malformed input, unusual spacing, and various color formats.

    export function parseBoxShadows(shadows: string): string {
      if (!shadows.trim()) {
        return "";
      }
    
      const valueAst = parse(shadows, { context: "value" });
      if (valueAst.type !== "Value") {
        return "";
      }
    
      const shadowsList: Array<List<CssNode>> = [];
      let currentShadow = new List<CssNode>();
    
      /* valueAst represent a Value node for box-shadow property, it will have children node such as
       * Identifier -> inset, Dimension -> length, Function -> rgb(), var(), Hash -> hex color,
       * Most importantly, in case of multiple shadows which are separated using comma, we have an Operator node
       * that represents the comma.
       *
       * To collect individual box-shadow, we need to collect all the nodes between each Operator node. For example,
       * box-shadow: 2px, 4px, #fff, 0px, 3px, rgb(0,0,0,0.5) will be represented as
       * [ Dimension, Dimension, Hash, Operator, Dimension, Dimension, Function ] and converted to
       * [ [Dimension, Dimension, Hash], [Dimension, Dimension, Function] ]
       */
      valueAst.children.forEach((child) => {
        if (child.type === "Operator" && child.value === ",") {
          shadowsList.push(currentShadow);
          currentShadow = new List<CssNode>();
        } else {
          currentShadow.push(child);
        }
      });
      shadowsList.push(currentShadow);
    
      return shadowsList.map((shadow) => parseBoxShadow(shadow)).join(", ");
    }
    
    /**
     * Parse a single box-shadow AST nodes into the following format:
     *   "[inset] offsetX offsetY blurRadius spreadRadius color"
     */
    function parseBoxShadow(shadowNodes: List<CssNode>) {
      let inset = false;
      let color: string | undefined = undefined;
      const dims: string[] = [];
    
      for (const node of shadowNodes) {
        switch (node.type) {
          case "Identifier":
            if (node.name.toLowerCase() === "inset") {
              inset = true;
            } else if (node.name.toLowerCase() === "currentcolor") {
              color = "currentcolor";
            }
            break;
    
          case "Function":
            if (!color && (node.name === "var" || node.name.startsWith("rgb"))) {
              color = generate(node);
            }
            break;
    
          case "Hash":
            if (!color) {
              color = `#${node.value}`;
            }
            break;
    
          case "Dimension":
            dims.push(generate(node));
            break;
    
          default:
            break;
        }
      }
    
      while (dims.length < 4) {
        dims.push("0px");
      }
    
      if (!color) {
        color = "currentcolor";
      }
    
      return [inset ? "inset" : "", dims[0], dims[1], dims[2], dims[3], color]
        .filter(Boolean)
        .join(" ");
    }

    @qodo-code-review
    Copy link

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Fix Promise type handling

    The params parameter is already a resolved object, not a Promise. Awaiting it
    unnecessarily complicates the code and could lead to unexpected behavior. Remove
    the await from the params parameter.

    plasmicpkgs-dev/app/[[...catchall]]/page.tsx [29-38]

     export default async function PlasmicLoaderPage({
       params,
     }: {
    -  params: Promise<Params>;
    +  params: Params;
     }) {
       const { prefetchedData, prefetchedQueryData } = await fetchData(
    -    (
    -      await params
    -    ).catchall
    +    params.catchall
       );
    • Apply / Chat
    Suggestion importance[1-10]: 8

    __

    Why: The suggestion correctly identifies and fixes a type mismatch: params is typed as a Promise<Params>, but in Next.js route handlers, params is already resolved. Removing the unnecessary await and correcting the type improves code correctness and prevents potential runtime errors.

    Medium
    Handle excess dimensions properly

    The current implementation doesn't handle the case where dimensions are provided
    after the color. This can lead to incorrect shadow rendering. Collect all
    dimensions first, then sort them into the correct positions regardless of their
    order in the input.

    platform/wab/src/wab/client/web-importer/css-utils.ts [43-90]

     function parseBoxShadow(shadowNodes: List<CssNode>) {
       let inset = false;
       let color: string | undefined = undefined;
       const dims: string[] = [];
     
       for (const node of shadowNodes) {
         switch (node.type) {
           case "Identifier":
             if (node.name.toLowerCase() === "inset") {
               inset = true;
             } else if (node.name.toLowerCase() === "currentcolor") {
               color = "currentcolor";
             }
             break;
     
           case "Function":
             if (!color && (node.name === "var" || node.name.startsWith("rgb"))) {
               color = generate(node);
             }
             break;
     
           case "Hash":
             if (!color) {
               color = `#${node.value}`;
             }
             break;
     
           case "Dimension":
             dims.push(generate(node));
             break;
     
           default:
             break;
         }
       }
     
    +  // Ensure we have exactly 4 dimensions, padding with 0px if needed
       while (dims.length < 4) {
         dims.push("0px");
       }
    +  // If we have more than 4 dimensions, only use the first 4
    +  const finalDims = dims.slice(0, 4);
     
       if (!color) {
         color = "currentcolor";
       }
     
    -  return [inset ? "inset" : "", dims[0], dims[1], dims[2], dims[3], color]
    +  return [inset ? "inset" : "", ...finalDims, color]
         .filter(Boolean)
         .join(" ");
     }
    • Apply / Chat
    Suggestion importance[1-10]: 5

    __

    Why: The suggestion ensures that only the first four dimensions are used for box-shadow, which is a minor but useful improvement for correctness and robustness. However, the original code already pads to four dimensions, and the change to slice excess is a small enhancement rather than a critical fix.

    Low
    General
    Fix inconsistent indentation

    The indentation in this function is inconsistent, with extra spaces before the
    if-statement. This can lead to confusion when reading the code and makes it
    harder to maintain. Fix the indentation to be consistent throughout the
    function.

    plasmicpkgs-dev/plasmic-register-dev-meta.ts [114-135]

     function replacePropsWithDevNames<T extends object>(node: T, regMap: ComponentRegistrationMap, visited: Set<object>): T {
       if (visited.has(node)) {
         return node;
       }
       visited.add(node);
     
    -    if (Array.isArray(node)) {
    -      node.forEach((childNode, index) => {
    -        // Handles:
    -        //   allowedComponents: [ "REPLACE-ME" ]
    -        if (typeof childNode === "string") {
    -          const reg = regMap.get(childNode);
    -          if (reg) {
    -            node[index] = reg.devName;
    -          }
    +  if (Array.isArray(node)) {
    +    node.forEach((childNode, index) => {
    +      // Handles:
    +      //   allowedComponents: [ "REPLACE-ME" ]
    +      if (typeof childNode === "string") {
    +        const reg = regMap.get(childNode);
    +        if (reg) {
    +          node[index] = reg.devName;
             }
    +      }
     
    -        if (typeof childNode === "object" && childNode !== null) {
    -          replacePropsWithDevNames(childNode, regMap, visited);
    -        }
    -      })
    -    } else {
    +      if (typeof childNode === "object" && childNode !== null) {
    +        replacePropsWithDevNames(childNode, regMap, visited);
    +      }
    +    })
    +  } else {
    • Apply / Chat
    Suggestion importance[1-10]: 3

    __

    Why: This suggestion only addresses code formatting (indentation), which improves readability but does not affect functionality or correctness. Its impact is minor.

    Low
    • More

    @willymwai willymwai merged commit 6500996 into truehostcloud:master May 22, 2025
    1 check passed
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    6 participants