widget-coherence

📁 groeimetai/snow-flow 📅 Jan 22, 2026
37
总安装量
8
周安装量
#10163
全站排名
安装命令
npx skills add https://github.com/groeimetai/snow-flow --skill widget-coherence

Agent 安装分布

claude-code 7
gemini-cli 6
antigravity 6
windsurf 6
codex 5
opencode 5

Skill 文档

Widget Coherence for ServiceNow Service Portal

Service Portal widgets MUST have perfect communication between Server Script, Client Controller, and HTML Template. This is not optional – widgets fail when these components don’t talk to each other correctly.

The Three-Way Contract

Every widget requires synchronized communication:

1. Server Script Must:

  • Initialize ALL data.* properties that HTML will reference
  • Handle EVERY input.action that client sends via c.server.get()
  • Return data in the format the client expects

2. Client Controller Must:

  • Implement EVERY method called by ng-click in HTML
  • Use c.server.get({action: 'name'}) for server communication
  • Update c.data when server responds

3. HTML Template Must:

  • Only reference data.* properties that server provides
  • Only call methods defined in client controller
  • Use correct Angular directives and bindings

Data Flow Patterns

Server → Client → HTML

// SERVER SCRIPT
(function() {
  data.incidents = [];
  data.loading = true;

  var gr = new GlideRecord('incident');
  gr.addQuery('active', true);
  gr.setLimit(10);
  gr.query();

  while (gr.next()) {
    data.incidents.push({
      sys_id: gr.getUniqueValue(),
      number: gr.getValue('number'),
      short_description: gr.getValue('short_description')
    });
  }
  data.loading = false;
})();
// CLIENT CONTROLLER
api.controller = function($scope) {
  var c = this;

  c.selectIncident = function(incident) {
    c.selectedIncident = incident;
  };
};
<!-- HTML TEMPLATE -->
<div ng-if="data.loading">Loading...</div>
<div ng-if="!data.loading">
  <div ng-repeat="incident in data.incidents"
       ng-click="c.selectIncident(incident)">
    {{incident.number}}: {{incident.short_description}}
  </div>
</div>

Client → Server (Actions)

// CLIENT CONTROLLER
c.saveIncident = function() {
  c.server.get({
    action: 'save_incident',
    incident_data: c.formData
  }).then(function(response) {
    if (response.data.success) {
      c.data.message = 'Saved successfully';
    }
  });
};
// SERVER SCRIPT
if (input && input.action === 'save_incident') {
  var gr = new GlideRecord('incident');
  gr.initialize();
  gr.setValue('short_description', input.incident_data.short_description);
  data.new_sys_id = gr.insert();
  data.success = !!data.new_sys_id;
}

Validation Checklist

Before deploying a widget, verify:

  • Every data.property in server is used in HTML or client
  • Every ng-click="c.method()" has matching c.method in client
  • Every c.server.get({action: 'x'}) has matching if(input.action === 'x') in server
  • No orphaned methods or unused data properties
  • All data.* properties are initialized in server (even if empty)

Common Failures

Action Name Mismatch

// CLIENT - sends 'saveIncident'
c.server.get({action: 'saveIncident'});

// SERVER - expects 'save_incident' (MISMATCH!)
if (input.action === 'save_incident') { }

Method Name Mismatch

<!-- HTML - calls saveData() -->
<button ng-click="c.saveData()">Save</button>
// CLIENT - defines save() (MISMATCH!)
c.save = function() { };

Undefined Data Properties

<!-- HTML - references user.email -->
<span>{{data.user.email}}</span>
// SERVER - only sets user.name (user.email is undefined!)
data.user = { name: userName };

Angular Directives Reference

Directive Purpose
ng-if Conditionally render element
ng-show/ng-hide Toggle visibility (element stays in DOM)
ng-repeat Iterate over array
ng-click Handle click events
ng-model Two-way data binding
ng-class Dynamic CSS classes
ng-disabled Disable form elements